#include <os/fat.h>
#include <os/fatunicode.h>
#include <os/fatfs.h>
#include <os/file.h>
#include <os/path.h>
#include <os/dir.h>
#include <os/diskman.h>
#include <os/diskio.h>
#include <os/walltime.h>
#include <os/diskio.h>
#include <os/mbr.h>
#include <os/gpt.h>
#include <os/fsinfo.h>
#include <os/config.h>
#include <lib/type.h>
#include <lib/stddef.h>
#include <lib/string.h>
#include <lib/math.h>

static file_status_t MoveWindows(fatfs_t *fs, lba_t sec);
static int CheckChar(char *str, int chr);
static uint32_t CheckFs(fatfs_t *fs, lba_t sec);
static int TestGptHeader(uint8_t *header);
static int Dbc2nd(uint8_t c);
static int Dbc1st(uint8_t c);
static file_status_t DirSetIdx(dir_obj_t *dir, uint64_t off);
static file_status_t DirNext(dir_obj_t *dir, int stretch);
static file_status_t CreateName(dir_obj_t *dir, const char **path);
static lba_t Clust2Sector(fatfs_t *fs, uint64_t clust);
static uint16_t XdirSum(const uint8_t *dir);
static file_status_t DirAlloc(dir_obj_t *dir, uint8_t num);
static file_status_t DirClear(fatfs_t *fs, uint32_t clust);
static file_status_t DirRead(dir_obj_t *dir);
static int MakeRand(uint32_t seed, uint8_t *buff, uint32_t n);
static file_status_t FollowPath(dir_obj_t *dir, const char *path);
static void GenerateNumName(uint8_t *des, uint8_t *src, uint16_t *lfn, uint32_t seq);
static uint8_t PutUtf(uint32_t ch, uint8_t *buff, uint32_t size);
static file_status_t PutFat(fatfs_t *fs, uint64_t clust, uint64_t value);
static uint64_t GetFat(file_id_t *obj, uint64_t clust);
static file_status_t DirFind(dir_obj_t *dir);
static void InitAllocInfo(fatfs_t *fs, file_id_t *obj);
static int CmpLFN(uint16_t *lfnbuff, uint8_t *dir);
static uint16_t XnameSum(const uint16_t *name);
static uint32_t LoadClust(fatfs_t *fs, uint8_t *dir);
static file_status_t ChangeBitmap(fatfs_t *fs, uint32_t clust, uint32_t nclust, int val);
static uint32_t FindBitmap(fatfs_t *fs, uint32_t clust, uint32_t num);
static void CreateXdir(uint8_t *dir, const uint16_t *lfn);
static uint8_t SumSFN(uint8_t *dir);
static void PutLFN(const uint16_t *lfn, uint8_t *dir, uint8_t ord, uint8_t sum);
static int GetVolNumber(const char *path);
static uint32_t XSum32(uint8_t data, uint32_t sum);
static int PickLFN(uint16_t *lfnbuff, uint8_t *dir);

static fatfs_t *fatfs[FS_VOLUME_MAX];                                             // volume fs object
static mbr_partition_t mbr_pt[4];                                                 // mbr partition table
static uint32_t cur_vol = 0;                                                      // current active vol
static uint32_t vol_id = 0;                                                       // alloc volume id
static const uint8_t lfnoffs[] = {1, 3, 5, 7, 9, 14, 16, 18, 20, 22, 24, 28, 30}; /* FAT: Offset of LFN characters in the directory entry */
static const uint8_t guid_ms_basic[16] = {0xA2, 0xA0, 0xD0, 0xEB, 0xE5, 0xB9, 0x33, 0x44, 0x87, 0xC0, 0x68, 0xB6, 0xB7, 0x26, 0x99, 0xC7};

uint16_t lfnbuff[FS_LFN_BUFF_MAX];
uint8_t dirbuff[MAXDIR(FS_LFN_LEN)];

static int CheckChar(char *str, int chr)
{
    while (*str && *str != chr)
        str++;
    return *str != '\0';
}

// check assign sector whether is fat vbr or not
// return: 0:Fat vbr 1:exFat vbr 2:Valid BS but no FAT 3:Invalid BS 4:Vol error
static uint32_t CheckFs(fatfs_t *fs, lba_t sec)
{
    bpb_t *bpb;

    // invaidate windows
    fs->wflags = 0;
    fs->winsec = -1;

    if (MoveWindows(fs, sec) != FSTU_OK)
        return FS_DISKERR;

    bpb = (bpb_t *)fs->win;

    // magic no valid
    if (bpb->magic != 0xAA55)
        return FS_INVAILDBS;

    if (!memcmp(((xbpb_t *)(fs->win))->jmp, "\xEB\x76\x90"
                                            "EXFAT   ",
                11))
        return FS_EXFATVBR;

    if (!memcmp(bpb->exbpb.fat32.sysid_string, "FAT32   ", 8))
        return FS_FATVBR;

    return FS_NOFAT;
}

// check a fat volume
static uint32_t CheckVolume(fatfs_t *fs, int disk)
{
    uint32_t fmt;
    int i;

    // check volume vbr if is FAT　VBR

    fmt = CheckFs(fs, 0);
    return fmt;
}

static uint32_t Tchar2Uni(const char **str)
{
    uint32_t uc;
    const uint8_t *p = *str;

    // ASCII/OEM input
    uint8_t b;
    uint16_t wc;
    wc = (uint8_t)*p++;

    if (Dbc1st((uint8_t)wc)) // is DBC 1st bytes
    {
        b = (uint8_t)*p++;
        if (!Dbc2nd((uint8_t)wc)) // invalid DBC 2nd bytes
            return 0xFFFFFFFF;
        wc = wc << 8 + b;
    }

    if (wc)
    {
        wc = FsOem2Uni(wc, FS_CODE_PAGE); // ASCII/OEM->Unicode
        if (!wc)
            return 0xFFFFFFFF;
    }
    uc = wc;

    *str = p; // next read point
    return uc;
}

static uint32_t Crc32(uint32_t crc, uint8_t data)
{
    uint8_t b;

    for (b = 1; b; b << 1)
    {
        crc ^= (data & b) ? 1 : 0;
        crc = (crc & 1) ? crc >> 1 ^ 0xEDB88320 : crc >> 1;
    }
    return crc;
}

// check GPT header if is valid
static int TestGptHeader(uint8_t *header)
{
    int i, bcc;

    if (!memcmp(((gpt_header_t *)header)->signature, "EFI PART", 8))
    {
        if (((gpt_header_t *)header)->revision == 0x100)
        {
            if (((gpt_header_t *)header)->header_size == 0x5C)
            {
                for (i = 0; bcc = 0xFFFFFFFF; i < 92) // make bcc value
                {
                    bcc = Crc32(bcc, i - 16 < 4 ? 0 : header[i]);
                }
                if (~bcc != ((gpt_header_t *)header)->table_checksum)
                    return 0;
                if (((gpt_header_t *)header)->entry_size != 128) // gpt entry size
                    return 0;
                if (((gpt_header_t *)header)->entry_num > 128) // gpt entry numbers
                    return 0;
                return 1;
            }
        }
    }
    return 0;
}

static uint32_t GetFatTime()
{
    uint32_t val;
    // get time from system
    val = ((uint32_t)WALLTIME_WR_DATE(walltime.year, walltime.month, walltime.day) << 16) | ((uint32_t)WALLTIME_WR_TIME(walltime.hour, walltime.minute, walltime.second));
    return val;
}

static uint32_t FindBitmap(fatfs_t *fs, uint32_t clust, uint32_t num)
{
    uint8_t bm, bv;
    uint32_t i;
    uint32_t val, sclust, ctr;

    clust -= 2; // the first bit in bitmap is cluster 2
    if (clust >= fs->fat_entrys - 2)
        clust = 0;
    sclust = val = clust;
    ctr = 0;
    while (1)
    {
        if (MoveWindows(fs, fs->bitbase + val / 8 / SECTOR_SIZE) != FSTU_OK)
            return 0xFFFFFFFF;
        i = val / 8 % SECTOR_SIZE; // byte
        bm = 1 << (val % 8);       // mask
        do
        {
            do
            {
                bv = fs->win[i] & bm; // get a bit
                bm <<= 1;
                if (++val > fs->fat_entrys - 2)
                {
                    val = 0;
                    bm = 0;
                    i = SECTOR_SIZE;
                }

                if (!bv) // free cluster
                {
                    ctr++;
                    if (ctr == num) // check whether had find suffic cluster
                        return sclust + 2;
                }
                else
                {
                    sclust = val;
                    ctr = 0; // restart scan
                }
            } while (bm); // scan all bits of byte
            // from first bits start
            bm = 1;
        } while (++i < SECTOR_SIZE); // scan all bytes of sector
    }
    return 0xFFFF;
}

// remove a director entry
static file_status_t DirRemove(dir_obj_t *dir)
{
    file_status_t res;
    fatfs_t *fs = dir->obj.fs;

    uint32_t last = dir->off;

    res = (dir->block_off == 0xFFFFFFFF) ? FSTU_OK : DirSetIdx(dir, dir->block_off); // goto the top of block
    if (res == FSTU_OK)
    {
        do
        {
            res = MoveWindows(fs, dir->sector);
            if (res != FSTU_OK)
                break;
            if (fs->fs_type == FS_EXFAT)
            {
                ((xdir_info_t *)(dir->dir))->type &= ~0x7F; // clear the entry use flags
            }
            else
            {
                ((dir_info_t *)(dir->dir))->name[0] = DDEM; // mask the entry "delect"
            }
            fs->wflags = 1;
            if (dir->off >= last)
                break;             // reached the last entry
            res = DirNext(dir, 0); // next entry
        } while (res == FSTU_OK);

        if (res == FSTU_NOT_FILE)
            res = FSTU_INT_ERR;
    }

    return res;
}

// write fs object win buff to disk
static file_status_t SyncWindows(fatfs_t *fs)
{
    file_status_t stu = FSTU_OK;

    if (fs->wflags) // wflags was set,sync data to disk
    {
        // write disk
        if (DiskWrite(fs->pydrv, fs->win, fs->winsec, 1) == DSTU_OK)
        {
            fs->wflags = 0;
            stu = FSTU_OK;
        }
        else
            stu = FSTU_DISK_ERR;
    }
    return stu;
}

// move sector windows to new sector
static file_status_t MoveWindows(fatfs_t *fs, lba_t sec)
{
    file_status_t stu = FSTU_OK;

    // check windows sec if is change
    if (sec != fs->winsec)
    {
        stu = SyncWindows(fs); // flush windows
        if (stu == FSTU_OK)
        {
            // fill sector windows with new data frome driver
            if (DiskRead(fs->pydrv, fs->win, sec, 1) != DSTU_OK)
            {
                fs->winsec = -1;
                stu = FSTU_DISK_ERR;
            }
            else
            {
                // update fs winsec
                fs->winsec = sec;
            }
        }
        else
            stu = FSTU_DISK_ERR;
    }
    return stu;
}

// get asign path volume number
static int GetVolNumber(const char *path)
{
    char *pt, *pp;
    char *pc;
    int vol = -1; // vol number
    int i = FS_VOLUME_MAX;

    pt = pp = (char *)path;
    if (!pt)
        return vol;

    do
    {
        // check path search char ':'
        pc = pt++;
    } while (*pc != ':' && *pc != '\0');

    if (*pc == ':')
    {
        // dight type volume id
        // such as:  0:/test
        if (isdigit(*pp) && (pp + 2) == pt)
        {
            i = *pp - '0';
        }
        else
        {
            // char style volume id
            // such as A:/test
            if (IS_CHAR(*pp) && (pp + 2) == pt)
                i = *pp - 'A';
        }

        // check volume number is vaild
        if (i < FS_VOLUME_MAX)
        {
            vol = i;
        }
        return vol;
    }
}

// get volume number and mount a volume
static file_status_t MountVolume(const char *path, fatfs_t **rfs, uint8_t mode)
{
    int vol = -1;
    fatfs_t *fs;
    int stu;
    lba_t bsec;
    bpb_t *bpb;
    uint64_t fatsize, totalsec, resvsec, syssec;
    uint64_t fatsz;
    uint64_t clust;
    uint8_t fmt;
    fsinfo_t *fsinfo;

    // get volume
    vol = GetVolNumber(path);
    if (vol < 0)
        return FSTU_INVALID_OBJ;
#if DEBUG_FS
    KPrint("[fat] mount vol %d\n", vol);
#endif
    // get volume fs object
    fs = fatfs[vol];
    if (!fs)
        return FSTU_INVALID_OBJ;
    *rfs = fs;
    // clear read mode
    mode &= ~FA_READ;
    // volume had mount
    if (fs->fs_type != 0)
    {
        stu = DiskStatus(fs->pydrv);
        // disk status ok
        if (!(stu & DSTU_NOINIT))
        {
            if (mode && (stu & DSTU_PROTECT))
                return FSTU_WRITE_PROTECT;
#if DEBUG_FS
            KPrint("[fat] path %s had been mounted to type %d!\n", path, (uint32_t)fs->fs_type);
#endif
            return FSTU_OK;
        }
        else
        {
            KPrint("[fat] mount: disk no init!\n");
            return DSTU_NOINIT;
        }
    }
    fs->fs_type = 0;
    // driver num is same as logical volume(may be is direct disk)
    fs->pydrv = vol;
    // init disk
    stu = DiskOpen(fs->pydrv);
    // disk no init or no disk
    if (stu == DSTU_NOINIT || stu == DSTU_NODISK)
    {
        KPrint("[fatfs] mount: disk open failed!\n");
        return FSTU_NOT_READY;
    }
    if (mode && (stu & DSTU_PROTECT))
        return FSTU_WRITE_PROTECT;

    // check volume filesystem if is valid
    fmt = CheckVolume(fs, vol);
    if (fmt == FS_INVAILDBS)
    {
        KPrint("[fat] volume check err\n");
        return FSTU_DISK_ERR;
    }
    bsec = fs->winsec; // volume location

    // an EXFAT volume found
    if (fmt == 1)
    {
        KPrint("[fatfs] mount fstype is EXFAT\n");
        uint64_t maxlba;
        uint64_t so, cv, clust;
        int i;
        xbpb_t *xbpb = (xbpb_t *)fs->win;

        for (i = 0; i < 53 && (!(xbpb->unused1[i])); i++)
            ;
        if (i < 53)
            return FSTU_NO_FILESYSTEM;
        // verison
        if (xbpb->vol_ver != 0x100)
            return FSTU_NO_FILESYSTEM;
        // sector size error
        if ((1 << xbpb->sector_size) != SECTOR_SIZE)
        {
            return FSTU_NO_FILESYSTEM;
        }
        maxlba = xbpb->total_size + bsec; // last sector of the volume
        if (maxlba >= 0x10000000)
            return FSTU_NO_FILESYSTEM;

        fs->fat_size = xbpb->fat_size; // number of sectors per fat
        fs->fat_num = xbpb->fat_count; // number of fat
        if (fs->fat_num != 1)
            return FSTU_NO_FILESYSTEM;
        fs->clust_size = 1 << xbpb->clust_size; // number of sectors per cluster
        if (!fs->clust_size)
            return FSTU_NO_FILESYSTEM;

        clust = xbpb->clust_count; // number of cluster
        // too many cluster
        if (clust > EXFAT_CLUST_MAX)
            return FSTU_NO_FILESYSTEM;
        fs->fat_entrys = clust + 2;

        fs->vol_start = bsec;
        fs->data_start = bsec + xbpb->clust_sector;
        fs->fat_start = bsec + xbpb->fat_sector;
        if (maxlba < (uint64_t)fs->data_start + clust * fs->clust_size)
            return FSTU_NO_FILESYSTEM;
        fs->dir_start = bsec + xbpb->rootdir_clust;
        // get bitmap location and check if it is contigous
        so = i = 0;
        while (1)
        {
            if (!i) // find bitmap in rootdir
            {
                if (so >= fs->clust_size)
                    return FSTU_NO_FILESYSTEM;
                if (MoveWindows(fs, Clust2Sector(fs, fs->dir_start) + so) != FSTU_NO_FILESYSTEM)
                    return FSTU_NO_FILESYSTEM;
                so++;
            }
            if (fs->win[i] == EXFAT_BITMAP) // is bitmap entry
                break;
            i = (i + DIR_TERN_SIZE) % SECTOR_SIZE; // next entry
        }
        // bitmap cluster
        clust = ((xdir_info_t *)(fs->win + i))->xdir_dir.xdir_bitmap.cluster;
        if (clust < 2 || clust >= fs->fat_entrys)
        {
            return FSTU_NO_FILESYSTEM;
        }
        fs->bitbase = fs->data_start + fs->clust_size * (clust - 2); // bitmap sectors
        while (1)
        {
            if (MoveWindows(fs, fs->fat_start + do_div64(clust, ((uint32_t)SECTOR_SIZE / 4))) != FSTU_OK)
                return FSTU_DISK_ERR;
            cv = *(uint32_t *)(fs->win + do_div(clust, (SECTOR_SIZE / 4)) * 4);
            if (cv == 0xFFFFFFFF) // last
                break;
            if (cv != ++clust) // fragment
                return FSTU_NO_FILESYSTEM;
        }
    }
    else
    {
#ifdef DEBUG_FS
        KPrint("[fat] mount fs type FAT\n");
#endif
        bpb = (bpb_t *)fs->win;
        // error volume mount
        if (bpb->bytes_per_sec != FS_SEC_MAX)
        {
            KPrint("[fat] mount: byte per sector error\n");
            return FSTU_NO_FILESYSTEM;
        }
        // sector size
        fs->sector_size = bpb->bytes_per_sec;
        // set fat size
        fatsize = bpb->fat_size16 ? bpb->fat_size16 : bpb->exbpb.fat32.fat_size32;
        fs->fat_size = fatsize;
        fs->fat_num = bpb->fat_num;
        // fat num must be 1 or 2
        if (fs->fat_num < 1 || fs->fat_num > 2)
        {
            KPrint("[fat] mount: fat num no valid!\n");
            return FSTU_NO_FILESYSTEM;
        }
        fatsize = fs->fat_size * fs->fat_num;
        fs->clust_size = bpb->sec_per_clust;
        // cluster size low is 2 sectors
        if (fs->clust_size < 2)
        {
            KPrint("[fat] mount: clust size < 2\n");
            return FSTU_NO_FILESYSTEM;
        }

        fs->rootdir_num = bpb->rootdir_number;
        // fs root dir tern number must be aligned to sector
        if (fs->rootdir_num % (FS_SEC_MAX / DIR_TERN_SIZE))
        {
            KPrint("[fat] mount: rootdir num no valid!\n");
            return FSTU_NO_FILESYSTEM;
        }

        totalsec = bpb->total_sector ? bpb->total_sector : bpb->large_sector;

        // reserved sectors no valid
        resvsec = bpb->reserved_sec;
        if (!resvsec)
        {
            KPrint("[fat] mount: reserved sector no valid!\n");
            return FSTU_NO_FILESYSTEM;
        }
        syssec = resvsec + fatsize + fs->rootdir_num / (FS_SEC_MAX / DIR_TERN_SIZE);
        if (totalsec < syssec)
        {
            KPrint("[fat] mount: totol sector %d no valid!\n", totalsec);
            return FSTU_NO_FILESYSTEM;
        }
        clust = do_div64((totalsec - syssec), fs->clust_size);
        if (!clust)
        {
            KPrint("[fat] mount: clust num no valid!\n");
            return FSTU_NO_FILESYSTEM;
        }
        // make sure fstype in according cluster numbers
        if (clust <= FAT32_CLUST_MAX)
            fmt = FS_FAT32;
        if (clust <= FAT16_CLUST_MAX)
            fmt = FS_FAT16;
        if (clust <= FAT12_CLUST_MAX)
            fmt = FS_FAT12;
        if (!fmt)
        {
            KPrint("[fat] mount: no found valid fmt!\n");
            return FSTU_NO_FILESYSTEM;
        }
        KPrint("[fat] mount: fs fmt is %x\n", fmt);
        // data area start clust is 2
        fs->fat_entrys = clust + 2;

        fs->vol_start = bsec;
        fs->fat_start = bsec + resvsec;
        fs->data_start = bsec + syssec;
        if (fmt == FS_FAT32) // fat32
        {
            // must be version 0
            if (bpb->exbpb.fat32.fat_ver != 0)
            {
                KPrint("[fat] mount: fat ver error,must be 0!\n");
                return FSTU_NO_FILESYSTEM;
            }
            if (fs->rootdir_num != 0)
            {
                KPrint("[fat] mount: rootdir num error,FAT32 no rootdir!\n", fs->rootdir_num);
                return FSTU_NO_FILESYSTEM;
            }
            fs->dir_start = bpb->exbpb.fat32.rootdir_clusters;
            fatsz = fs->fat_entrys * 4;
        }
        else
        {
            // fat 16,12
            if (!fs->rootdir_num)
            {
                KPrint("[fat] mount: rootdir number error!\n");
                return FSTU_NO_FILESYSTEM;
            }
            fs->dir_start = fs->fat_start + fatsize;
            fatsz = (fmt == FS_FAT16) ? fs->fat_entrys * 2 : do_div64(fs->fat_entrys * 3, 2) + (fs->fat_entrys & 1);
        }

        if (fs->fat_size < (DIV_ROUND_UP(fatsz, SECTOR_SIZE)))
        {
            KPrint("[fat] mount: total fat size %d small than one sectors!\n", fs->fat_size);
            return FSTU_NO_FILESYSTEM;
        }

        // deal with fsinfo according system config
        fs->last_clust = fs->free_clust = 0xffffffff;
        fs->fsinfo_flags = 0x80;
        KPrint("fsinfo sector %d\n", bpb->exbpb.fat32.fsinfo_sector);
        // only fat32 and bpb fsinfo sector is 1 allow to update fsinfo
        if (fmt == FS_FAT32 && (bpb->exbpb.fat32.fsinfo_sector == 1) && MoveWindows(fs, bsec + 1) == FSTU_OK)
        {
            fs->fsinfo_flags = 0;
            fsinfo = (fsinfo_t *)fs->win;
            if (fsinfo->signature == 0xAA550000 && fsinfo->lead_signature == 0x41615252 && fsinfo->another_signature == 0x61417272)
            {
                fs->fsinfo_start = 1;
                fs->free_clust = fsinfo->free_cluster;
                fs->last_clust = fsinfo->next_cluster;
#if DEBUG_FS
                KPrint("[fatfs] mount FAT32 free clust %d last clust %d\n", (uint32_t)fs->free_clust, (uint32_t)fs->last_clust);
#endif
            }
            else
            {
                KPrint("[fatfs] mount FAT32 get fsinfo block err\n");
            }
        }
    }
    // set fs info
    fs->fs_type = fmt;
    fs->vol_id = vol_id++;
    fs->lfn_buff = lfnbuff;
    fs->dir_buff = dirbuff;
    fs->cur_dir = 0; // init current dir
    KPrint("[fat] mount volume ok\n");
    return FSTU_OK;
}

// check MBR header
static int CheckMBRHeader(fatfs_t *fs, lba_t sec)
{
    mbr_t *mbr;

    if (MoveWindows(fs, sec) != FSTU_OK)
        return 4;
    mbr = (mbr_t *)(fs->win);
    if (mbr->magic != 0xAA55)
        return 3;
    if (mbr->reserved != 0x00)
        return 3;
    return 0;
}

// sync fsinfo struct to disk
static file_status_t SyncFs(fatfs_t *fs)
{
    file_status_t stu;
    fsinfo_t *fsinfo;

    stu = SyncWindows(fs);
    if (stu == FSTU_OK)
    {
        // fat32 whether update fsinfo
        if (fs->fs_type == FS_FAT32 && fs->fsinfo_flags)
        {
            if (MoveWindows(fs, fs->fsinfo_start) == FSTU_OK)
            {
                // clear fs object sector windows
                memset((void *)fs->win, 0, sizeof(fs->win));
                // write new data to windows
                fsinfo = (fsinfo_t *)fs->win;
                fsinfo->signature = FSINFO_SIGN;
                fsinfo->lead_signature = FSINFO_LEND_SIGN;
                fsinfo->another_signature = FSINFO_ANOTHER_SIGN;
                fsinfo->free_cluster = fs->free_clust;
                fsinfo->next_cluster = fs->last_clust;
                //KPrint("write fsinfo to %d\n", fs->winsec);
                if (DiskWrite(fs->pydrv, fs->win, fs->winsec, 1) == DSTU_OK)
                {
                    fs->fsinfo_flags = 0;
                    return FSTU_OK;
                }
            }
            return FSTU_DISK_ERR;
        }
        return FSTU_OK;
    }
    return FSTU_DISK_ERR;
}

static lba_t Clust2Sector(fatfs_t *fs, uint64_t clust)
{
    clust -= 2; // start clust is 2
    if (clust >= fs->fat_entrys - 2)
        return -1;
    return fs->data_start + fs->clust_size * clust;
}

static file_status_t VertifyObj(file_id_t *obj, fatfs_t **fs)
{
    file_status_t res;

    if (obj && obj->fs && obj->fs->fs_type && obj->vol_id == obj->fs->vol_id)
    {
        if (!(DiskStatus(obj->fs->pydrv) & DSTU_NOINIT))
        {
            res = FSTU_OK;
        }
    }
    *fs = (res == FSTU_OK) ? obj->fs : 0;
    return res;
}

static int MakeRand(uint32_t seed, uint8_t *buff, uint32_t n)
{
    uint32_t r;

    if (!seed)
        seed = 1;
    do
    {
        for (r = 0; r < 8; r++)
        {
            seed = seed & 1 ? seed >> 1 ^ 0xA3000000 : seed >> 1;
            *buff++ = seed;
        }
    } while (--n);
    return seed;
}

static void InitAllocInfo(fatfs_t *fs, file_id_t *obj)
{
    obj->clust = ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.cluster;    // start cluster
    obj->size = ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.size;        // file size
    obj->status = ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.flags & 2; // alloc status
    obj->n_frag = 0;                                                                 // no fragment info
}

// load a directory entry block
static file_status_t LoadXdir(dir_obj_t *dir)
{
    file_status_t res;
    uint32_t i, sz;
    uint8_t *pdir = dir->obj.fs->dir_buff;

    // load file director entry
    res = MoveWindows(dir->obj.fs, dir->sector);
    if (res != FSTU_OK)
        return res;

    // no exFAT file/director entry
    if (((xdir_info_t *)(dir->dir))->type != EXFAT_FILEDIR)
        return FSTU_INT_ERR;

    // copy director entry
    memcpy(pdir + 0 * DIR_TERN_SIZE, dir->dir, DIR_TERN_SIZE);
    sz = ((xdir_info_t *)(dir))->xdir_dir.xdir_primary.num_dir * DIR_TERN_SIZE;
    if (sz > 3 * DIR_TERN_SIZE || sz > 19 * DIR_TERN_SIZE)
        return FSTU_INT_ERR;

    // load stream extension entry
    res = DirNext(dir, 0);
    if (res == FSTU_NOT_FILE)
        res = FSTU_INT_ERR;
    if (res != FSTU_OK)
        return res;
    res = MoveWindows(dir->obj.fs, dir->sector);
    if (res != FSTU_OK)
        return res;
    if (((xdir_info_t *)(dir->dir))->type != EXFAT_STREAM)
        return FSTU_INT_ERR;
    memcpy(pdir + 1 * DIR_TERN_SIZE, dir->dir, DIR_TERN_SIZE);
    if (MAXDIR(((xdir_info_t *)(pdir))->xdir_dir.xdir_seconday.name_len) > sz)
        return FSTU_INT_ERR;

    // load file-name entry
    i = 2 * DIR_TERN_SIZE; // name offset
    do
    {
        res = DirNext(dir, 0); // next entry
        if (res == FSTU_NOT_FILE)
            res == FSTU_INT_ERR;
        if (res != FSTU_OK)
            return res;
        res = MoveWindows(dir->obj.fs, dir->sector);
        if (res != FSTU_OK)
            return FSTU_INT_ERR;

        // check dir entry whether above limit
        if (i < MAXDIR(FS_LFN_LEN))
        {
            memcpy(pdir + i, dir->dir, DIR_TERN_SIZE);
        }
    } while ((i += DIR_TERN_SIZE) < sz);

    if (i <= MAXDIR(FS_LFN_LEN))
    {
        if (XdirSum(pdir) != ((xdir_info_t *)(dir))->xdir_dir.xdir_primary.chksum)
        {
            return FSTU_INT_ERR;
        }
    }
    return FSTU_OK;
}

// create partition in MBR/GPT table
static file_status_t CreatePartition(uint8_t drv, const lba_t *plist, uint8_t sys, uint8_t *buff)
{
    uint32_t i;
    lba_t drv_size;
    file_status_t res;
    uint32_t drv_size32, data_start32, data_size32;
    uint32_t backup_start;
    uint8_t *pte;
    uint32_t sector, head, cylider;

    if (DiskIoCtl(drv, GET_SECTOR_COUNT, &drv_size) != DSTU_OK)
        return FSTU_DISK_ERR;

    if (drv_size >= FS_GPT_MIN) // create partition in GPT
    {
        uint16_t sector_size;
        uint32_t pt_size, align;
        uint32_t bcc, rnd, pi, si;
        uint32_t data_start, data_size;
        uint32_t pool_size;
        uint32_t off;

        static const uint8_t gpt_mbr[16] = {0x00, 0x00, 0x02, 0x00, 0xEE, 0xFE, 0xFF, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF};

        sector_size = SECTOR_SIZE;

        align = GPT_ALIGN / SECTOR_SIZE;                    // partition aligned[sector]
        pt_size = GPT_ITEMS * GPT_ENTRY_SIZE / SECTOR_SIZE; // size of partition table[sector]
        backup_start = drv_size - pt_size - 1;              // backup partition table start
        data_start = 2 + pt_size;                           // first data sector
        pool_size = backup_start - data_start;              // data area size
        bcc = 0xFFFFFFFF;
        data_size = 1;

        pi = si = 0; // partition table index,size table size
        while (pi < GPT_ITEMS)
        {
            if (!(pi * GPT_ENTRY_SIZE % SECTOR_SIZE))
                memset((void *)buff, 0, SECTOR_SIZE);
            if (data_size != 0)
            {
                data_size = (data_start + align - 1) & (0 - align); // align partition start
                data_size = plist[si++];                            // get a partition size
                if (data_size <= 100)
                {
                    data_size = pool_size * data_size / 100;
                    data_size = (data_size + align - 1) & (0 - align); // align partition size
                }
                if (data_start + data_size > backup_start) // above pool limit
                {
                    data_size = (data_start < backup_start) ? backup_start - data_start : 0;
                }
                if (data_size != 0)
                {
                    off = pi * GPT_ENTRY_SIZE % SECTOR_SIZE;
                    memcpy(((gpt_partition_t *)(buff + off))->type_guid, (uint8_t *)guid_ms_basic, 16); // type GUID
                    rnd = MakeRand(rnd, ((gpt_partition_t *)(buff + off))->part_guid, 16);              // partition GUID
                    ((gpt_partition_t *)(buff + off))->start_lba = data_start;                          // partition start lba
                    ((gpt_partition_t *)(buff + off))->end_lba = data_start + data_size;                // partition end lba
                    data_start += data_size;                                                            // next partition start
                }
                if ((pi + 1) * GPT_ENTRY_SIZE % SECTOR_SIZE) // write buffer if is fill up sector
                {
                    for (i = 0; i < SECTOR_SIZE; bcc = Crc32(bcc, buff[i++]))
                        ;

                    if (DiskWrite(drv, buff, 2 + pi * GPT_ENTRY_SIZE / SECTOR_SIZE, 1) != DSTU_OK)
                        return FSTU_DISK_ERR;
                    if (DiskWrite(drv, buff, backup_start + pi * GPT_ENTRY_SIZE / SECTOR, 1) != DSTU_OK)
                        return FSTU_DISK_ERR;
                }
            }
            pi++; // next partition entry
        }
        // create primary GPT header
        memset(buff, 0, SECTOR_SIZE);
        memcpy(((gpt_header_t *)buff)->signature, "EFI PART", 8);          // header signature
        ((gpt_header_t *)buff)->revision = 0x100;                          // header revision
        ((gpt_header_t *)buff)->header_size = 0x5C;                        // header size
        ((gpt_header_t *)buff)->table_checksum = ~bcc;                     // partition table check sum
        ((gpt_header_t *)buff)->header_lba = 1;                            // header lba
        ((gpt_header_t *)buff)->backup_lba = drv_size - 1;                 // backup header lba
        ((gpt_header_t *)buff)->first_lba = data_start;                    // first allocatable lba
        ((gpt_header_t *)buff)->last_lba = backup_start - 1;               // last allocatable lba
        ((gpt_header_t *)buff)->entry_size = GPT_ENTRY_SIZE;               // partition entry size
        ((gpt_header_t *)buff)->entry_num = GPT_ITEMS;                     // partition entry number
        ((gpt_header_t *)buff)->table_lba = 2;                             // partition table start
        rnd = MakeRand(rnd, ((gpt_header_t *)buff)->disk_guid, 16);        // disk guid
        for (i = 0, bcc = 0xFFFFFFFF; i < 92; bcc = Crc32(bcc, buff[i++])) // calc header checksum
            ;
        ((gpt_header_t *)buff)->header_checksum = ~bcc; // header checksum
        if (DiskWrite(drv, buff, 1, 1) != DSTU_OK)
            return FSTU_DISK_ERR;

        // create backup GPT header
        ((gpt_header_t *)buff)->header_lba = drv_size - 1; // header lba
        ((gpt_header_t *)buff)->backup_lba = 1;            // backup lba
        ((gpt_header_t *)buff)->table_lba = backup_start;  // table start lba
        for (i = 0, bcc = 0xFFFFFFFF; i < 92; bcc = Crc32(bcc, buff[i++]))
            ;
        ((gpt_header_t *)buff)->header_checksum = ~bcc; // header checksum
        if (DiskWrite(drv, buff, drv_size - 1, 1) != DSTU_OK)
            return FSTU_DISK_ERR;

        // create protective MBR
        memset(buff, 0, SECTOR_SIZE);
        memcpy(((mbr_t *)buff)->partition, (uint8_t *)gpt_mbr, 16); // create a GPT parition
        ((mbr_t *)buff)->magic = 0xAA55;                            // magic
        if (DiskWrite(drv, buff, 0, 1) != DSTU_OK)
            return FSTU_DISK_ERR;
    }
    else
    {
        // create partition in MBR
        drv_size32 = drv_size;
        sector = SEC_PER_TRACK;
        for (head = 8; head != 0 && drv_size / head / sector > 1024; head *= 2)
            ;
        if (!head)
            head = 255; // number of heads needs to be <256
        memset(buff, 0, SECTOR_SIZE);
        pte = (uint8_t *)((mbr_t *)buff)->partition;
        // create partition
        for (i = 0, data_start32 = sector; i < 4 && data_start32 != 0 && data_start32 < drv_size32; i++, data_start32 += data_size32)
        {
            data_size32 = plist[i];
            if (data_size32 <= 100)
                data_size32 = (data_size32 == 100) ? drv_size32 : drv_size32 / 100 * drv_size32;
            if (data_start32 + data_size32 >= drv_size32 || data_start32 + data_size32 < data_start32)
                data_size32 = drv_size32 - data_size32;
            if (!data_size32)
                break;

            ((mbr_partition_t *)pte)->lba = data_start32;                   // partition start lba
            ((mbr_partition_t *)pte)->sectors = data_start32 + data_size32; // partition end lba
            ((mbr_partition_t *)pte)->type = sys;                           // partiton type

            cylider = (data_start32 / sector / head); // startcylinder
            head = (data_start32 / sector % head);    // start head
            sector = (data_start32 % sector + 1);     // start sector
            ((mbr_partition_t *)pte)->start_chs[0] = head;
            ((mbr_partition_t *)pte)->start_chs[1] = (cylider >> 2 & 0xC0) | sector;
            ((mbr_partition_t *)pte)->start_chs[2] = cylider;

            cylider = (data_start32 + data_size32 - 1) / sector / head; // end cylinder
            head = (data_start32 + data_size32 - 1) / sector % head;    // end head
            sector = (data_start32 + data_size32 - 1) % sector + 1;     // end sector
            ((mbr_partition_t *)pte)->end_chs[0] = head;
            ((mbr_partition_t *)pte)->end_chs[1] = (cylider >> 2 & 0xC0) | sector;
            ((mbr_partition_t *)pte)->end_chs[2] = cylider;

            pte += MBR_ENTRY_SIZE; // next partition entry
        }
        ((mbr_t *)buff)->magic = 0xAA55; // magic
        if (DiskWrite(drv, buff, 0, 1) != DSTU_OK)
            return FSTU_DISK_ERR;
    }
    return FSTU_OK;
}

static void GetXFileInfo(uint8_t *dir, file_info_t *fno)
{
    uint16_t wc, hs;
    uint32_t di, si, nc;
    // get file name from entry block
    si = DIR_TERN_SIZE * 2;
    nc = 0;
    hs = 0;
    di = 0;
    while (nc < ((xdir_info_t *)dir)->xdir_dir.xdir_primary.num_dir)
    {
        if (si >= MAXDIR(FS_LFN_LEN)) // truncated director block
        {
            di = 0;
            break;
        }
        if (!(si % DIR_TERN_SIZE)) // skip entry type
            si += 2;
        wc = *(uint16_t *)(dir + si); // get a character
        si += 2;                      // next charater
        nc++;
        if (!hs && IsSurrogate(wc)) // is a surrogate
        {
            hs = wc; // get low surrogate
            continue;
        }
        wc = PutUtf((uint32_t)hs >> 16 | wc, &fno->fname[di], FS_LFN_BUFF_LEN - di);
        if (!wc) // buffer overflow or wrong encode
        {
            di = 0;
            break;
        }
        di += wc;
        hs = 0;
    }
    if (hs) // broken surrogate
        di = 0;
    if (!di) // inaccessable object name
        fno->fname[di++] = '?';
    fno->fname[di] = 0; // terminate object name

    fno->altname[0] = 0; // exFAT no support SFN
    fno->attr = ((xdir_info_t *)dir)->xdir_dir.xdir_primary.attr;
    fno->size = ((xdir_info_t *)dir)->xdir_dir.xdir_seconday.lenght;
    fno->time = ((xdir_info_t *)dir)->xdir_dir.xdir_primary.mtime & 0xff;
    fno->date = ((xdir_info_t *)dir)->xdir_dir.xdir_primary.mtime >> 16 & 0xff;
}

// get fileinfo
static void GetFileInfo(dir_obj_t *dir, file_info_t *fno)
{
    uint32_t si, di;
    uint16_t wc, hs;
    uint8_t lcf;
    fatfs_t *fs = dir->obj.fs;

    memset(fno, 0, sizeof(file_info_t));

    fno->fname[0] = 0; // invalid file infop

    if (!(dir->sector))
        return;

    if (fs->fs_type == FS_EXFAT)
    {
        GetXFileInfo(fs->dir_buff, fno); // get file info on exFAT
        return;
    }
    else
    {
        // on the FAT/FAT32 volume
        si = di = hs = 0;
        if (dir->block_off != 0xffffffff) // get LFN
        {
            while (fs->lfn_buff[si] != 0)
            {
                wc = fs->lfn_buff[si++]; // get an LFN character(UTF-16)

                wc = PutUtf(wc, &fno->fname[di], FS_LFN_BUFF_MAX - 1); // store it in UTF-16 or UTF8 encoding
                if (!wc)                                               // invalid char or buffer overflow
                {
                    di = 0;
                    break;
                }
                di += wc;
            }
            fno->fname[di] = 0; // LFN terminate
        }
    }
    // make SFN
    si = di = 0;
    while (si < 11) // get SFN from SFN entry
    {
        wc = dir->dir[si++]; // get a char
        if (wc == ' ')       // skip sapce
            continue;
        if (wc == RDDEM)
            wc = DDEM;
        if (si == 9 && di < FS_SFN_BUFF_LEN)
            fno->altname[di++] = (char)'.'; // insert '.' if extension exist

        fno->altname[di++] = wc; // store it without any conversion
    }
    fno->altname[di] = 0; // SFN terminate

    if (!(fno->fname[0])) // LFN invalid
    {
        if (!di)
        {
            fno->fname[di++] = (char)'?';
        }
        else
        {
            for (si = di = 0, lcf = NS_BODY; fno->altname[si]; si++, di++) // copy altname to fname
            {
                wc = (uint16_t)fno->altname[si];
                if (wc == (char)'.')
                {
                    lcf = NS_EXT;
                }
                if (IsUpper(wc) && (((dir_info_t *)(dir->dir))->flag & lcf))
                {
                    wc += 0x20;
                }
                fno->fname[di] = wc;
            }
        }
    }
    // get file info
    fno->attr = ((dir_info_t *)(dir->dir))->attr;  // fize attribute
    fno->size = *(uint32_t *)((dir->dir) + 28);    // file size
    fno->time = ((dir_info_t *)(dir->dir))->mtime; // file modified time
    fno->date = ((dir_info_t *)(dir->dir))->mdate; // file modified date
#if DEBUG_FS
    KPrint("[fat] file info\n");
    KPrint("[fat] altname %s ", fno->altname);
    KPrint("[fat] fname %s ", fno->fname);
    KPrint("[fat] attr 0x%x ", fno->attr);
    KPrint("[fat] size %d ", fno->size);
    KPrint("[fat] time %d:%d:%d ", FAT_TIME_HOUR(fno->time), FAT_TIME_MIN(fno->time), FAT_TIME_SEC(fno->time));
    KPrint("[fat] date %d/%d/%d ", FAT_TIME_YEAR(fno->date), FAT_TIME_MON(fno->date), FAT_TIME_DAY(fno->date));
    KPrint("\n");
#endif
    return;
}

/**set/clear a block of allocation bitmap
 * @fs: filesystem object
 * @clust: cluster number to change from
 * @nclust: number of clusters to be changed
 * @val: bit value to be set
 * */
static file_status_t ChangeBitmap(fatfs_t *fs, uint32_t clust, uint32_t nclust, int val)
{
    uint8_t bmp;
    uint32_t i;
    lba_t sector;

    clust -= 2;
    sector = fs->bitbase + clust / 8 / SECTOR_SIZE; // sector address
    i = clust / 8 % SECTOR_SIZE;                    // byte offset in sector
    bmp = 1 << (clust % 8);                         // bmp mask

    while (1)
    {
        if (MoveWindows(fs, sector++) != FSTU_OK)
        {
            return FSTU_DISK_ERR;
        }

        do
        {
            do
            {
                if (val == (int)((fs->win[i] & bmp)))
                {
                    return FSTU_INT_ERR; // is the bit expect value
                }
                fs->win[i] ^= bmp;
                fs->wflags = 1;
                if (!(--nclust))
                {
                    return FSTU_OK;
                }
            } while (bmp <<= 1); // next bit
            bmp = 1;
        } while (++i < SECTOR_SIZE); // next byte
        i = 0;
    }
}

// fill the first fragment of the FAT china
static file_status_t FillFirstFrag(file_id_t *obj)
{
    file_status_t res;
    uint32_t clust, n;

    if (obj->status == 3)
    {
        // has the object been changed fragmented
        for (clust = obj->clust, n = obj->n_count; n; clust++, n--)
        {
            // create clustere chain
            res = PutFat(obj->fs, clust, clust + 1);
            if (res != FSTU_OK)
            {
                return res;
            }
            obj->status = 0; // change status 'FAT china is valid'
        }
    }
}

// fill the last fragment of the FAT chain
static file_status_t FillLastFrag(file_id_t *obj, uint32_t lclust, uint32_t val)
{
    file_status_t res;

    while (obj->n_frag > 0)
    {
        // create chain of last fragment
        res = PutFat(obj->fs, lclust - obj->n_frag + 1, (obj->n_frag > 1) ? lclust - obj->n_frag + 2 : val);
        if (res != FSTU_OK)
            return res;
        obj->n_frag--;
    }
    return FSTU_OK;
}

static file_status_t RemoveChain(file_id_t *obj, uint32_t clust, uint32_t pclust)
{
    fatfs_t *fs = obj->fs;
    file_status_t res;
    uint32_t next;
    uint32_t sclust = clust, eclust = clust;

    if (clust < 2 || clust >= fs->fat_entrys)
        return FSTU_INT_ERR; // clust invalid

    // mark the previous cluster to EOF
    if (pclust != 0 || fs->fs_type != FS_EXFAT || obj->status != 2)
    {
        res = PutFat(fs, pclust, 0xFFFFFFFF);
        if (res != FSTU_OK)
            return res;
    }

    // follow cluster chain
    do
    {
        next = GetFat(obj, clust);
        if (next == 0) // empty
            break;
        if (next == 1) // internal error
            return FSTU_INT_ERR;
        if (next == 0xFFFFFFFF)
            return FSTU_DISK_ERR; // disk error
        if (fs->fs_type != FS_EXFAT)
            res = PutFat(fs, clust, 0); // mark the clust to free
        if (res != FSTU_OK)
            return res;
        if (fs->free_clust < fs->fat_entrys - 2) // update FSINFO block
        {
            fs->free_clust++;
            fs->fsinfo_flags |= 1;
        }

        if (eclust + 1 == next)
        {
            // is next cluster contiguous
            eclust = next;
        }
        else
        {
            // end of contiguous cluster block
            if (fs->fs_type == FS_EXFAT)
            {
                res = ChangeBitmap(fs, sclust, eclust - sclust + 1, 0); // mark the cluster free
                if (res != FSTU_OK)
                {
                    return res;
                }
            }

            sclust = eclust = next;
        }

        clust = next; // next clust
    } while (clust < fs->fat_entrys);

    if (fs->fs_type == FS_EXFAT)
    {
        if (!pclust) // has the entries chain been removed
        {
            obj->status = 0; // change the chain status 'initial'
        }
        else
        {
            if (obj->status == 0) // is it a fragmented chain from the begining
            {
                clust = obj->clust; // follow the chain to check if is gets contiguous
                while (clust != pclust)
                {
                    next = GetFat(obj, clust);
                    if (next < 2)
                        return FSTU_INT_ERR;

                    if (next == 0xffffffff)
                        return FSTU_DISK_ERR;

                    if (next != clust + 1)
                        break; // not contiguous
                }

                if (clust == pclust) // has the chain got contiguous again
                {
                    obj->status = 2; // change the chain status 'contiguous'
                }
            }
            else
            {
                if (obj->status == 3 && pclust >= obj->clust && pclust <= obj->clust + obj->n_count)
                {
                    obj->status = 2; // change the chain status 'contiguous'
                }
            }
        }
    }

    return FSTU_OK;
}

static file_status_t DirRead(dir_obj_t *dir)
{
    file_status_t res = FSTU_OK;
    uint8_t b, attr;
    uint8_t ord = 0xFF, sum = 0xFF;
    fatfs_t *fs = dir->obj.fs;

    while (dir->sector)
    {
        res = MoveWindows(fs, dir->sector);
        if (res != FSTU_OK)
            break;

        memset(fs->lfn_buff, 0, 13 * 2);

        b = ((dir_info_t *)(dir->dir))->name[0]; // get entry type
        if (!b)
        {
            KPrint("[fat] read dir reach to end!\n");
            res = FSTU_NOT_FILE; // reached the end of the director
            break;
        }

        if (fs->fs_type == FS_EXFAT) // on the ExFat volume
        {
            if (b == EXFAT_FILEDIR) // start of the file entry block
            {
                dir->block_off = (address_t)dir->dir; // get location of block
                res = LoadXdir(dir);                  // load the entry block
                if (res == FSTU_OK)
                {
                    dir->obj.attr = ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_primary.attr & ATT_MASK; // get attribute
                }
                break;
            }
        }
        else
        {
            // on the FAT/FAT32 volume
            dir->obj.attr = attr = ((dir_info_t *)(dir->dir))->attr;

            if (b == DDEM || b == '.' || attr == ATT_VOL) // invalid entry
            {
                ord = 0xFF;
            }
            else
            {
                if (attr == ATT_LFN) // LFN entry
                {
                    KPrint("[fat] found lfn entires\n");
                    if (b & LLEF) // last entry
                    {
                        sum = ((dir_info_lfn_t *)(dir->dir))->checksum;
                        b &= ~LLEF;
                        ord = b; // get order of LFN
                        dir->block_off = (address_t)dir->off;
                    }
                    ord = PickLFN(fs->lfn_buff, dir->dir) ? ord - 1 : 0xFF;
                    res = DirNext(dir, 0); // skip to SFN entry
                }
                else
                {                                            // SFN entry
                    if (ord != 0 || sum != SumSFN(dir->dir)) // is valid SFN
                        dir->block_off = 0xFFFFFFF;
                }
                break; // only read one entires
            }
        }
        res = DirNext(dir, 0); // next entry
        if (res != FSTU_OK)
            break;
    }
    if (res != FSTU_OK) // read error
        dir->sector = 0;
    return res;
}

// load the object director entry block
static file_status_t LoadObjXdir(dir_obj_t *dir, const file_id_t *obj)
{
    file_status_t res;

    // open object containing director
    dir->obj.fs = obj->fs;
    dir->obj.clust = obj->c_clust;
    dir->obj.status = (uint8_t)obj->c_size;
    dir->obj.size = obj->c_size & 0xFFFFFF00;
    dir->obj.n_frag = 0;
    dir->block_off = obj->c_off;

    res = DirSetIdx(dir, dir->block_off); // goto object entry block
    if (res == FSTU_OK)
        res = LoadXdir(dir); // load the object entry block
    return res;
}

// store the directory entry block
static file_status_t StoreXdir(dir_obj_t *dir)
{
    file_status_t res;
    uint8_t nentry;
    uint8_t *pdir = dir->obj.fs->dir_buff;

    // create set sum
    ((xdir_info_t *)(pdir))->xdir_dir.xdir_primary.chksum = XdirSum(pdir);
    nentry = ((xdir_info_t *)(pdir))->xdir_dir.xdir_primary.num_dir + 1; // xdir seconary entry numbers

    // store the director entry block to the director
    res = DirSetIdx(dir, dir->block_off);
    while (res == FSTU_OK)
    {
        res = MoveWindows(dir->obj.fs, dir->sector);
        if (res != FSTU_OK)
        {
            break;
        }
        memcpy(dir->dir, pdir, DIR_TERN_SIZE);
        dir->obj.fs->wflags = 1;
        if (!(--nentry))
            break;
        pdir += DIR_TERN_SIZE;
        res = DirNext(dir, 0); // next seconary entry
    }
    return (res == FSTU_OK || res == FSTU_DISK_ERR) ? res : FSTU_INT_ERR;
}

static file_status_t FollowPath(dir_obj_t *dir, const char *path)
{
    file_status_t res;
    fatfs_t *fs = dir->obj.fs;
    int ns;

    // skip volume
    while (*path != '/')
    {
        path++;
    }

#if DEBUG_FS
    KPrint("[fat] follow path %s fs->type %d\n", path, (uint32_t)fs->fs_type);
#endif

    if (*path != '/' && *path != '\\') // without head separtor
        dir->obj.clust = fs->cur_dir;  // get current root director
    else
    {
        while (*path == '/' || *path == '\\') // with head separtor
            path++;
        dir->obj.clust = 0; // root
    }

    dir->obj.n_frag = 0; // invalid last fragment counter of the object
    if (fs->fs_type == FS_EXFAT && dir->obj.clust)
    {
        // retrieve sub-director status
        dir_obj_t dj;

        dir->obj.c_clust = fs->cd_clust;
        dir->obj.c_size = fs->cd_size;
        dir->obj.c_off = fs->cd_off;
        res = LoadObjXdir(&dj, &dir->obj);
        if (res != FSTU_OK)
        {
            return res;
        }
        dir->obj.size = ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.size;
    }

    if (*path <= ' ') // no filename
    {
        dir->fn[NSFLAGS] |= NS_NONAME;
        res = DirSetIdx(dir, 0); // origin director
    }
    else // follow path
    {
        while (1)
        {
            res = CreateName(dir, &path);
            if (res != FSTU_OK)
            {
                KPrint("[fat] create name failed!\n");
                return -1;
            }
            ns = dir->fn[NSFLAGS];
            res = DirFind(dir);
            if (res != FSTU_OK) // failed to find object
            {
                if (res == FSTU_NOT_FILE) // no file object
                {
                    if (ns & NS_DOT) // if exist DOT name
                    {
                        if (!(ns & NS_LAST)) // if no last segment
                            continue;
                        dir->fn[NSFLAGS] = NS_NONAME;
                        res = FSTU_OK;
                    }
                    else // can not find object
                    {
                        if (!(ns & NS_LAST)) // if no is last segment adjust err
                            res = FSTU_NOT_FILE;
                    }
                }
                break;
            }

            // get attr info
            dir->obj.attr = ((dir_info_t *)(dir->dir))->attr;

            if (ns & NS_LAST) // is last path
            {
                break;
            }

            if (!(dir->obj.attr & ATT_FDIR)) // if not is sub-director
            {
                KPrint("[fat] follow dir no is a sub director!\n");
                res = FSTU_OK; // follow path end
                break;
            }

            if (fs->fs_type == FS_EXFAT)
            {
                // save containing directory info for next dir
                dir->obj.c_clust = dir->obj.clust;
                dir->obj.c_off = dir->block_off;
                dir->obj.c_size = ((uint32_t)(dir->obj.size & 0xffffff00)) | dir->obj.status;
                InitAllocInfo(fs, &dir->obj); // open next director
            }
            else
            {
                dir->obj.clust = LoadClust(fs, fs->win + (uint32_t)(dir->off) % SECTOR_SIZE); // open the next director
            }
        }
    }
    return res;
}

static uint32_t CreateChain(file_id_t *obj, uint32_t clust)
{
    uint32_t sclust, cstu, nclust;
    file_status_t res;
    fatfs_t *fs = obj->fs;

    if (!clust) // create new cluster
    {
        sclust = fs->last_clust;
        if (!sclust || sclust >= fs->fat_entrys)
            sclust = 1;
    }
    else
    {
        // check cluster status
        cstu = GetFat(obj, clust); // get cluster status
        if (cstu < 2)
        {
            KPrint("[fat] next clust error!\n");
            return cstu;
        }
        if (cstu < fs->fat_entrys) // already follow next cluster
            return cstu;
        sclust = clust; // start at assign clust
    }

    if (!(fs->free_clust)) // no free clu@ster
    {
        KPrint("[fat] no free clust on fs!\n");
        return 0;
    }

    if (fs->fs_type == FS_EXFAT) // on the exFAT volum
    {
        nclust = FindBitmap(fs, sclust, 1); // find free cluster
        if (nclust == 0 || nclust == 0xFFFFFFFF)
        {
            return nclust;
        }

        res = ChangeBitmap(fs, nclust, 1, 1); // change the cluster to 'in user'
        if (res == FSTU_INT_ERR)
            return 1;
        if (res == FSTU_DISK_ERR)
            return 1;

        if (!clust) // is a new clust
        {
            obj->status = 2;
        }
        else
        {
            // stretch chain
            if (obj->status == 2 && nclust != sclust + 1)
            {                                       // is the chain got fragment
                obj->n_count = sclust - obj->clust; // set size of contiguous part
                obj->status = 3;                    // change status 'just fragmented'
            }
        }
        if (obj->status == 2)
        {
            // is file no-contiguous
            if (nclust = clust + 1)
            {
                obj->n_frag = obj->n_frag ? obj->n_frag + 1 : 2; // increment size of last fragment
            }
            else
            {
                if (!obj->n_frag)
                    obj->n_frag = 1;
                res = FillLastFrag(obj, clust, nclust);
                if (res == FSTU_OK)
                    obj->n_frag = 1;
            }
        }
    }
    else
    {
        nclust = 0;
        // on the FAT/FAT32 volumea
        if (sclust == clust) // stretch cluster
        {
            nclust = sclust + 1;          // test if next cluster free
            if (nclust >= fs->fat_entrys) // limit
                nclust = 2;
            cstu = GetFat(obj, nclust); // get cluster status
            if (cstu == 1 && cstu == 0xFFFFFFFF)
                return cstu;
            if (cstu != 0) // no free
            {
                cstu = fs->last_clust; // get last cluster
                if (cstu >= 2 && cstu <= fs->fat_entrys)
                {
                    sclust = cstu; // cluster to start to find
                }
                nclust = 0;
            }
        }

        if (!nclust) // new clust no free and find another
        {
            nclust = sclust; // start cluster
            while (1)
            {
                nclust++;                     // next cluster
                if (nclust >= fs->fat_entrys) // check wrap-around
                {
                    nclust = 2;
                    if (nclust > sclust) // no found free
                        return 0;
                }

                cstu = GetFat(obj, nclust); // get cluster status
                if (cstu == 0)
                    break;
                if (cstu < 2 && cstu == 0xFFFFFFFF)
                    return cstu;
                if (nclust == sclust) // no free cluster
                    return 0;
            }
        }

        // set FAT cluster chain
        res = PutFat(fs, nclust, 0xFFFFFFFF);
        if (res == FSTU_OK && clust != 0)
        {
            res = PutFat(fs, clust, nclust);
        }
    }

    // update fsinfo
    if (res == FSTU_OK)
    {
        fs->last_clust = nclust;
        if (fs->free_clust <= fs->fat_entrys - 2) // alloc a free cluster
        {
            fs->free_clust--;
        }
        fs->fsinfo_flags |= 1; // FSINFO had been modified
    }
    else
    {
        nclust = (res == FSTU_DISK_ERR) ? 0xFFFFFFFF : 1;
    }

    return nclust; // return new cluster
}

static file_status_t DirClear(fatfs_t *fs, uint32_t clust)
{
    lba_t sector;
    uint32_t size = 0, n = 0;
    uint8_t *buff = NULL;

    if (SyncWindows(fs) != FSTU_OK) // flush disk window
        return FSTU_DISK_ERR;

    sector = Clust2Sector(fs, clust); // get top of the  cluster
    fs->winsec = sector;              // set window to top of the cluster

    // alloc a temp buff to over multi-sector
    for (size = (((fs->clust_size * SECTOR_SIZE) >= MALLOC_MAX) ? MALLOC_MAX : (fs->clust_size * SECTOR_SIZE)), buff = 0; size > SECTOR_SIZE && !(buff = KMemAlloc(size)); size /= 2)
        ;

    if (size > SECTOR_SIZE) // alloc succeed
    {
        memset(buff, 0, size);
        size /= SECTOR_SIZE;        // byte->sector
        while (n <= fs->clust_size) // repeat write sector block until cluster end
        {
            if (DiskWrite(fs->pydrv, buff, sector + n, size) != DSTU_OK)
                break;
            n += size; // next block
        }
        KMemFree(buff); // free buffer
        return FSTU_OK;
    }
    else
    {
        // ues window buffer
        buff = fs->win;
        size = 1;
        memset(fs->win, 0, SECTOR_SIZE);
        while (n < fs->clust_size) // repeate write buff to sector
        {
            if (DiskWrite(fs->pydrv, buff, sector + n, size) != DSTU_OK)
                break;
            n++; // next sector
        }
        return ((n == fs->clust_size) ? FSTU_OK : FSTU_DISK_ERR);
    }
    return FSTU_OK;
}

static uint32_t LoadClust(fatfs_t *fs, uint8_t *dir)
{
    uint32_t clust;

    clust = ((dir_info_t *)dir)->clust_low;
    if (fs->fs_type == FS_FAT32)
        clust |= (((uint32_t)(((dir_info_t *)dir)->clust_high)) << 16);
    return clust;
}

static void SetClust(fatfs_t *fs, uint8_t *dir, uint32_t clust)
{
    ((dir_info_t *)dir)->clust_low = (uint16_t)clust & 0xFFFF;
    if (fs->fs_type == FS_FAT32)
    {
        ((dir_info_t *)dir)->clust_high = (uint16_t)(clust >> 16);
    }
}

static file_status_t DirRegister(dir_obj_t *dir)
{
    file_status_t res;
    fatfs_t *fs = dir->obj.fs;
    int n, sum;
    uint32_t len, entry_num;
    uint8_t sn[12];

    if (dir->fn[NSFLAGS] & (NS_DOT | NS_NONAME)) // check name if valid
        return FSTU_INVALID_NAME;

    len = strlen((uint8_t *)fs->lfn_buff); // get LFN len

    if (fs->fs_type == FS_EXFAT)
    {
        entry_num = (len + 14) / 15 + 2; // number of entries to allocation
        res = DirAlloc(dir, entry_num);
        if (res != FSTU_OK)
            return res;
        dir->block_off = (uint32_t)(dir->dir - DIR_TERN_SIZE * (entry_num - 1)); // set the allocated entry block offset

        if (dir->obj.status & 4)
        {
            // has director been stretched by new alloction
            dir->obj.status &= ~4;
            res = FillFirstFrag(&dir->obj);
            if (res != FSTU_OK)
                return res;

            if (dir->obj.clust)
            {
                // is a sub-directory
                dir_obj_t dj;

                res = LoadObjXdir(&dj, &dir->obj); // load object status
                if (res != FSTU_OK)
                    return res;

                dj.obj.size = fs->clust_size * SECTOR_SIZE; // increase director size by cluster size
                ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.size = dir->obj.size;
                ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.lenght = dir->obj.size;
                ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.flags = dir->obj.status | 1; // update the allocation status
                res = StoreXdir(dir);                                                                // store director
                if (res != FSTU_OK)
                    return res;
            }
        }

        CreateXdir(fs->dir_buff, fs->lfn_buff); // create director block
        return FSTU_OK;
    }
    else
    {
        // FAT
        memcpy(sn, dir->fn, 12);
        if (sn[NSFLAGS] & NS_LOSS) // when SFN out of 8.3 format,general a number name
        {
#if DEBUG_FS
            KPrint("[fat] name out of 8.3\n");
#endif
            dir->fn[NSFLAGS] = NS_NOLFN; // find SFN only
            for (n = 0; n < 100; n++)
            {
                GenerateNumName(dir->fn, sn, fs->lfn_buff, n); // generate a number name
                res = DirFind(dir);                            // check if the name collides with exist SFN
                if (res != FSTU_OK)
                    break;
            }
            if (n == 100) // too many collisions
                return FSTU_DENIED;
            if (res != FSTU_NOT_FILE) // other no-collision
                return res;
            dir->fn[NSFLAGS] = sn[NSFLAGS];
        }

        // create LFN entry
        entry_num = ((sn[NSFLAGS] & NS_LFN) ? (((len + 12) / 13) + 1) : 1);
        if (entry_num == 0) // repair the entry num
            entry_num = 1;

        res = DirAlloc(dir, entry_num); // alloc entry
        if (res == FSTU_OK && --entry_num > 0)
        {
            res = DirSetIdx(dir, dir->off - (entry_num * DIR_TERN_SIZE));
            if (res == FSTU_OK)
            {
                sum = SumSFN(dir->fn); // checksum value
                do
                {
                    res = MoveWindows(fs, dir->sector);
                    if (res != FSTU_OK)
                        break;
                    PutLFN(fs->lfn_buff, dir->dir, entry_num, sum); // store LFN
                    fs->wflags = 1;
                    res = DirNext(dir, 0); // next entry
                } while (res == FSTU_OK && --entry_num > 1);
            }
        }

        // set SFN entry
        if (res == FSTU_OK)
        {
            res = MoveWindows(fs, dir->sector);
            if (res == FSTU_OK)
            {
                memset(dir->dir, 0, DIR_TERN_SIZE);
                memcpy(((dir_info_t *)(dir->dir))->name, dir->fn, 11); // put SFN

                ((dir_info_t *)(dir->dir))->flag = dir->fn[NSFLAGS] & (NS_BODY | NS_EXT); // put NT flags
                fs->wflags = 1;
            }
        }
        return res;
    }
}

static void GenerateNumName(uint8_t *des, uint8_t *src, uint16_t *lfn, uint32_t seq)
{
    uint32_t sreq;
    uint8_t ns[8], c;
    uint32_t i = 0, j = 0;
    uint16_t wc;

    memcpy(des, src, 11);

    if (seq > 5) // get hash value
    {
        sreq = seq;
        while (*lfn)
        {
            wc = *lfn++;
            for (i = 0; i < 16; i++)
            {
                sreq = (sreq << 1) + (wc & 1);
                wc >>= 1;
                if (sreq & 0x10000)
                    sreq ^= 0x11021;
            }
            seq = sreq;
        }
    };
    // create number to string
    i = 7;
    do
    {
        c = seq % 16 >= 0xA ? seq % 16 + 'A' : seq % 16 + '0';
        ns[i--] = c;
        seq /= 16;
    } while (seq != 0);
    ns[i] = '~';

    // go SFN after and replace space to number
    while (des[j] != ' ' && j < i)
    {
        if (j == i - 1)
            break;
        j++;
    }

    // copy number to SFN after
    while (j < 8)
    {
        des[j++] = (i < 8) ? ns[i++] : ' ';
    }
}

static file_status_t DirFind(dir_obj_t *dir)
{
    file_status_t res = FSTU_OK;
    fatfs_t *fs = dir->obj.fs;
    uint8_t flags;
    uint8_t ord, sum;
    uint8_t attr;

    res = DirSetIdx(dir, 0); // rewind
    if (res != FSTU_OK)
    {
        KPrint("[fat] dir find set idx failed!\n");
        return res;
    }

    if (fs->fs_type == FS_EXFAT) // exFAT
    {
        uint8_t nc;
        uint32_t di, ni;
        uint16_t hash = XnameSum(fs->lfn_buff); // hash value of the name to find

        while ((res == DirRead(dir)) == FSTU_OK)
        {
            // read an item
            if (((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.name_chksum != hash)
            {
                continue;
            }

            // check name (skip two attr director entry in first)
            for (nc = ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.name_len, di = DIR_TERN_SIZE * 2; ni = 0; nc--, di += 2, ni++)
            {

                if (!(di % DIR_TERN_SIZE))
                {
                    di += 2;
                }

                // compare the name
                if (FsWtoupper(*(uint16_t *)(fs->dir_buff + di)) != FsWtoupper(fs->lfn_buff[ni]))
                    break;
            }
            return res;
        }
    }

    ord = sum = 0xFF;
    dir->block_off = 0xFFFFFFFF;
    dir->obj.attr = attr = ((dir_info_t *)(dir->dir))->attr;
    // FAT
    do
    {
        res = MoveWindows(fs, dir->sector);
        if (res != FSTU_OK)
            break;

        flags = ((dir_info_t *)(dir->dir))->name[0];
        if (!flags) // reach to the end of table
        {
#if DEBUG_FS
            KPrint("[fat] no found file!\n");
#endif
            res = FSTU_NOT_FILE;
            break;
        }
        attr = ((dir_info_t *)(dir->dir))->attr; // get file attr

        if (((flags == DDEM) && ((attr == ATT_VOL) && (attr != ATT_LFN)))) // invalid data
        {
            KPrint("[fat] invalid data,file attr error!\n");
            ord = 0xFF; // reset LFN
            dir->off = 0xFFFFFFFF;
        }
        else
        {
            if (attr == ATT_LFN) // LFN entry found
            {
                if (!(dir->fn[NSFLAGS] & NS_NOLFN))
                {
                    if (flags & LLEF) // is start of LFN entry
                    {
                        sum = ((dir_info_lfn_t *)(dir->dir))->checksum;
                        flags &= ~LLEF; // LFN start order
                        ord = flags;
                        dir->block_off = dir->off; // LFN start off
                    }
                    ord = CmpLFN(fs->lfn_buff, ((uint8_t *)dir->dir)) ? ord - 1 : 0xFF;
                    // KPrint("LFN ord %d,sum %d\n", ord, sum);
                }
            }
            else // SFN entry found
            {
                if (ord == 0) // LFN match
                {
                    res = FSTU_OK;
                    break;
                }

                if (!memcmp(dir->dir, dir->fn, 11)) // SFN match
                {
                    res = FSTU_OK;
                    break;
                }

                ord = 0xFF; // reset LFN
                dir->block_off = 0xFFFFFFFF;
            }
        }
        res = DirNext(dir, 0); // next entry
    } while (res == FSTU_OK);
    return res;
}

// pick a part of LFN
// return： 0:buffer overflow or invalid LFN entry 1：succeed
static int PickLFN(uint16_t *lfnbuff, uint8_t *dir)
{
    int i, s;
    uint16_t uc, wc;

    memset(lfnbuff, 0, 13 * 2);
    wc = 1;
    if (((dir_info_lfn_t *)dir)->start != 0)
        return 0;

    i = ((((dir_info_lfn_t *)dir)->order & ~LLEF) - 1) * 13; // get offset in LFN buffer
    for (s = 0; s < 13; s++)
    {
        uc = *(uint16_t *)(dir + lfnoffs[s]);
        if (wc != 0)
        {
            if (i >= FS_LFN_LEN + 1) // buffer overflow
                return 0;
            lfnbuff[i++] = wc = uc; // store uchar
        }
        else
        {
            if (uc != 0xFFFF) // check filler
                return 0;
        }
    }
    if ((((dir_info_lfn_t *)dir)->order & LLEF) && uc != 0) // last part of LFN and no terminated
    {
        if (i >= FS_LFN_LEN + 1) // buffer overflow
            return 0;
        lfnbuff[i] = 0;
#if DEBUG_FS
        KPrint("[fat] PickLFN: pick lfn end!\n");
#endif
    }
    return 1;
}

static void PutLFN(const uint16_t *lfn, uint8_t *dir, uint8_t ord, uint8_t sum)
{
    int i, s;
    uint16_t wc;

    ((dir_info_lfn_t *)dir)->checksum = sum;
    ((dir_info_lfn_t *)dir)->attr = ATT_LFN;
    ((dir_info_lfn_t *)dir)->type = 0;
    ((dir_info_lfn_t *)dir)->start = 0;
    // get LFN off
    i = (ord - 1) * 13;
    s = wc = 0;

    while (s < 13)
    {
        if (wc != 0xFFFF)
            wc = lfn[i++];

        *(uint16_t *)(dir + lfnoffs[s]) = wc;
        if (!wc)
        {
            wc = 0xFFFF;
        }
        s++;
    }

    if (wc == 0xFFFF || !lfn[i]) // last LFN part
    {
#if DEBUG_FS
        KPrint("[fat] PutLFN: ord %d is last LFN!\n", ord);
#endif
        ord |= LLEF;
    }

    ((dir_info_lfn_t *)dir)->order = ord; // set LFN order
}

static file_status_t DirNext(dir_obj_t *dir, int stretch)
{
    uint32_t off;
    fatfs_t *fs = dir->obj.fs;
    uint32_t clust;

    off = dir->off + DIR_TERN_SIZE;                                       // next entry
    if (off >= ((fs->fs_type == FS_EXFAT) ? EXFAT_DIR_MAX : FAT_DIR_MAX)) // above limit
    {
        dir->sector = 0;
        KPrint("[fat] dir next: off %d too big!\n", off);
        return FSTU_NOT_FILE; // EOF
    }

    if (!(off % SECTOR_SIZE)) // sector change(just is offset out of sector size)
    {
        dir->sector++; // next sector
        // check clust limit
        if (!dir->clust) // static table
        {
            if (off / DIR_TERN_SIZE >= fs->rootdir_num) // reach end of table
            {
                dir->sector = 0;
                KPrint("[fat] dir next: off reach at fs->rootdir_num\n");
                return FSTU_NOT_FILE;
            }
        }
        else // dynamic table
        {
            if (!(off / SECTOR_SIZE & (fs->clust_size - 1))) // clust change
            {
                clust = GetFat(&dir->obj, dir->clust); // get next clust
                if (clust <= 1)
                    return FSTU_INT_ERR;
                if (clust == 0xFFFFFFFF)
                    return FSTU_DISK_ERR;
                if (clust < fs->fat_entrys)
                {
                    if (!stretch) // no stretch,EOF
                    {
                        dir->sector = 0;
                        return FSTU_NOT_FILE;
                    }
                    // create a chain
                    clust = CreateChain(&dir->obj, dir->clust);
                    if (clust == 0)
                        return FSTU_DENIED;
                    if (clust == 1)
                        return FSTU_INT_ERR;
                    if (clust == 0xFFFFFFFF)
                        return FSTU_DISK_ERR;
                    dir->obj.status |= 4; // exFAT the director had been stretch
                }
                dir->clust = clust;
                dir->sector = Clust2Sector(fs, clust);
            }
        }
    }
    // update info
    dir->dir = fs->win + (off % SECTOR_SIZE);
    dir->off = off;
    return FSTU_OK;
}

static file_status_t DirAlloc(dir_obj_t *dir, uint8_t num)
{
    file_status_t res;
    fatfs_t *fs = dir->obj.fs;
    int n = 0;

    res = DirSetIdx(dir, 0);
    if (res == FSTU_OK)
    {
        do
        {
            res = MoveWindows(fs, dir->sector);
            if (res != FSTU_OK)
                break;
            // check dir entries whether had alloced
            if ((fs->fs_type == FS_EXFAT) ? !(((xdir_info_t *)(dir->dir))->type & 0x80) : (((dir_info_t *)(dir->dir))->name[0] == DDEM || !(((dir_info_t *)(dir->dir))->name[0])))
            {
                if (++n == num) // found free director entry
                    break;
            }
            else
                n = 0;             // restart search
            res = DirNext(dir, 1); // next entry
        } while (res == FSTU_OK);
    }
    if (res == FSTU_NOT_FILE)
        res = FSTU_DENIED;
    return res;
}

static uint8_t SumSFN(uint8_t *dir)
{
    uint8_t sum = 0;
    uint32_t n = 11;

    do
    {
        sum = (sum >> 1) + (sum << 7) + *dir++;
    } while (--n);
    return sum;
}

static int CmpLFN(uint16_t *lfnbuff, uint8_t *dir)
{
    int i, s;
    uint16_t wc, uc;

    if (((dir_info_lfn_t *)(dir))->start) // check LFN first clust,must be 0
    {
        KPrint("[fat] %s: start clust %d no valid!\n", __func__, ((dir_info_lfn_t *)(dir))->start);
        return 0;
    }

    // get offset
    i = ((((dir_info_lfn_t *)(dir))->order & 0x3F) - 1) * 13;

    for (wc = 1, s = 0; s < 13; s++)
    {
        if (wc != 0)
        {
            uc = *(uint16_t *)(dir + lfnoffs[s]);                                  // pick a characters
            if (i >= FS_LFN_LEN + 1 || FsWtoupper(uc) != FsWtoupper(lfnbuff[i++])) // compare
            {
                return 0; // no match
            }
            wc = uc;
        }
    }

    if ((((dir_info_lfn_t *)(dir))->order & LLEF) && uc && lfnbuff[i]) // last segment match but length difference
    {
        KPrint("[fat] %s: lfn buff length error! lfnbuff %x\n", __func__, lfnbuff[i]);
        return 0;
    }
    return 1; // the part of LFN matched
}

static file_status_t DirSetIdx(dir_obj_t *dir, uint64_t off)
{
    fatfs_t *fs = dir->obj.fs;
    uint32_t clust, clust_size;

    // check the director entry is valid
    if (DIRIDX_VAILD(off))
    {
        KPrint("[fat] dir off %d no valid!\n", off);
        return FSTU_INT_ERR;
    }

    dir->off = off;         // set offset
    clust = dir->obj.clust; // get start clust
    if (clust == 0 && fs->fs_type == FS_FAT32)
    {
        clust = fs->dir_start; // root director cluster
    }

    if (clust == 0)
    {
        // static table(root-director on the FAT volume)
        if (off / DIR_TERN_SIZE >= fs->rootdir_num)
        {
            KPrint("[fat] off %d above limit %d\n", (uint32_t)off, (uint32_t)fs->rootdir_num);
            return FSTU_INT_ERR;
        }
        dir->sector = fs->dir_start; // get root director start
    }
    else
    {
        // start search clust
        clust_size = fs->clust_size * fs->sector_size;
        while (off >= clust_size)
        {
            clust = GetFat(&dir->obj, clust); // get clust next from fat
            if (clust == 0xffffffff)
                return FSTU_DISK_ERR;
            if (clust < 2 && clust >= fs->fat_entrys)
                return FSTU_INT_ERR;
            off -= clust_size;
        }
        // transtion cluster to sector
        dir->sector = Clust2Sector(fs, clust);
    }
    dir->clust = clust;
    if (!dir->sector)
        return FSTU_INT_ERR;
    dir->sector += (off / SECTOR_SIZE); // dir sector
    dir->dir = fs->win + (off % SECTOR_SIZE);
    return FSTU_OK;
}

// read a value from FAT
static uint64_t GetFat(file_id_t *obj, uint64_t clust)
{
    uint64_t val;
    uint64_t bc, wc;
    fatfs_t *fs = obj->fs;

    if (clust < 2 || clust >= fs->fat_entrys)
    {
        val = 1;
    }
    else
    {
        val = 0xffffffff; // default value on disk error
    }

    switch (fs->fs_type)
    {
    case FS_FAT12:
        bc = clust;
        bc += bc / 2;
        if (MoveWindows(fs, fs->fat_start + (bc / SECTOR_SIZE)) != FSTU_OK)
            break;
        wc = fs->win[bc++ % SECTOR_SIZE];
        if (MoveWindows(fs, fs->fat_start + (bc / SECTOR_SIZE)) != FSTU_OK)
            break;
        wc |= fs->win[bc % SECTOR_SIZE] << 8;
        val = (clust & 1) ? (wc >> 4) : (wc & 0xfff);
        break;
    case FS_FAT16:
        if (MoveWindows(fs, fs->fat_start + (clust / (SECTOR_SIZE / 2))) != FSTU_OK)
            break;
        val = *(uint16_t *)(fs->win + (clust * 2 % SECTOR_SIZE));
        break;
    case FS_FAT32:
        if (MoveWindows(fs, fs->fat_start + (clust / (SECTOR_SIZE / 4))) != FSTU_OK)
            break;
        val = *(uint32_t *)(fs->win + (clust * 4 % SECTOR_SIZE));
        break;
    default:
        val = 1;
        break;
    }
    return val;
}

static int Dbc1st(uint8_t c)
{
    if (c) // SBCS fixed code page
        return 0;
    return 0;
}

static int Dbc2nd(uint8_t c)
{
    if (c > 0)
        return 0;
    return 0;
}

static file_status_t PutFat(fatfs_t *fs, uint64_t clust, uint64_t value)
{
    uint64_t bc;
    uint8_t *p;
    file_status_t res = FSTU_OK;

    if (clust >= 2 && clust < fs->fat_entrys)
    {
        switch (fs->fs_type)
        {
        case FS_FAT12:
            bc = clust;
            bc += bc / 2;
            if (res = MoveWindows(fs, fs->fat_start + (bc / SECTOR_SIZE)) != FSTU_OK)
                break;
            p = fs->win + (bc++ % SECTOR_SIZE);
            *p = (clust & 1) ? ((uint8_t)value >> 4) : (uint8_t)value;
            fs->wflags = 1;
            if (res = MoveWindows(fs, fs->fat_start + (bc / SECTOR_SIZE)) != FSTU_OK)
                p = fs->win + (bc % SECTOR_SIZE);
            *p = (clust & 1) ? ((uint8_t)((value >> 4) >> 8) | (*p & 0xf0)) : ((*p & 0xf0) | ((uint8_t)(value >> 8) & 0x0f));
            fs->wflags = 1;
            break;
        case FS_FAT16:
            if (res = MoveWindows(fs, fs->fat_start + (clust / (SECTOR_SIZE / 2))) != FSTU_OK)
                break;
            *(uint16_t *)(fs->win + clust * 2 % SECTOR_SIZE) = (uint16_t)value;
            fs->wflags = 1;
            break;
        case FS_FAT32:
            if (res = MoveWindows(fs, fs->fat_start + (clust / (SECTOR_SIZE / 4))) != FSTU_OK)
                break;
            *(uint32_t *)(fs->win + clust * 4 % SECTOR_SIZE) = (uint32_t)value;
            fs->wflags = 1;
            break;
        default:
            break;
        }
    }
    return res;
}

file_status_t FileOpen(file_obj_t *fp, const char *path, uint32_t mode)
{
    fatfs_t *fs;
    dir_obj_t dir;
    file_status_t res = FSTU_OK;

    if (!fp)
        return FSTU_INVALID_OBJ;
    mode &= FA_READ | FA_WRITE | FA_CREATE_ALWAYS | FA_CREATE_NEW | FA_OPEN_ALWAYS | FA_OPEN_APPEND;

    if (mode & FA_OPEN_APPEND)
        mode |= (FA_SEEKED | FA_READ | FA_WRITE);

    res = MountVolume(path, &fs, mode); // mount volume
    if (res == FSTU_OK)
    {
        dir.obj.fs = fs;
        res = FollowPath(&dir, path); // follow path
        if (res == FSTU_OK)
        {
            if (dir.fn[NSFLAGS] & NS_NONAME) // invalid name
                res = FSTU_INVALID_NAME;
        }

        // create or open file
        if (mode & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS | FA_CREATE_NEW))
        {
            if (res != FSTU_OK) // no file
            {
                if (res == FSTU_NOT_FILE) // create a new dir entry(type: dir)
                {
                    res = DirRegister(&dir);
#if DEBUG_FS
                    KPrint("[fat] create new file!\n");
#endif
                }
                mode |= FA_CREATE_ALWAYS; // file is created
            }
            else // file exist
            {
                if (dir.obj.attr & (ATT_RDONLY | ATT_FDIR)) // cannot overwrite
                    res = FSTU_DENIED;
                else
                {
                    if (mode & FA_CREATE_NEW)
                        res = FSTU_EXIST; // exist and cannot create new file
                }
            }

            if (res == FSTU_OK && (mode & FA_CREATE_ALWAYS))
            {
                // set director entry initial
                uint8_t clust = LoadClust(fs, dir.dir);          // get current clust
                ((dir_info_t *)(dir.dir))->ctime = GetFatTime(); // get create time
                ((dir_info_t *)(dir.dir))->attr = ATT_ARCHIVE;   // reset attribute
                SetClust(fs, dir.dir, 0);                        // reset file allocation info
                ((dir_info_t *)(dir.dir))->size = 0;             // reset file size
                fs->wflags = 1;
                if (clust != 0) // remove clust
                {
                    lba_t sector = fs->winsec;
                    res = RemoveChain(&dir.obj, clust, 0);
                    if (res == FSTU_OK) // reset last clust
                    {
                        res = MoveWindows(fs, sector);
                        fs->last_clust = clust - 1;
                    }
                }
            }
        }
        else // open exist file
        {
            if (res == FSTU_OK)
            {
                if (((dir_info_t *)(dir.dir))->attr & ATT_FDIR) // when try open dir
                {
                    KPrint("[fat] open object is a dir,please to do FileOpenDir function \n");
                    res = FSTU_NOT_FILE;
                }
                else
                {
                    // try write mode open RDONLY file
                    if ((mode & FA_WRITE) && (((dir_info_t *)(dir.dir))->attr & ATT_RDONLY))
                    {
                        KPrint("[fat] try open only read file with FA_WRITE\n");
                        res = FSTU_DENIED;
                    }
                }
            }
        }

        if (res == FSTU_OK)
        {
            if (mode & FA_CREATE_ALWAYS)
            {
                mode |= FA_MODIFIED;
            }
            fp->dir_sec = fs->winsec;
            fp->dir_ptr = dir.dir;
        }

        // get file object info
        if (res == FSTU_OK)
        {
            if (fs->fs_type == FS_EXFAT) // EXFAT
            {
                // get containing director info
                fp->obj.c_clust = dir.obj.c_clust;
                fp->obj.c_size = ((uint32_t)dir.obj.size & 0xFFFFFF00) | dir.obj.status;
                fp->obj.c_off = dir.block_off;
            }
            else // FAT/FAT32
            {
                fp->obj.clust = LoadClust(fs, (uint8_t *)dir.dir); // get dir alloc info
                fp->obj.size = ((dir_info_t *)(dir.dir))->size;
#if DEBUG_FS
                KPrint("[fat] get file clust %d size %d \n", fp->obj.clust, fp->obj.size);
#endif
            }
        }
        fp->cltbl = 0;               // fastseek support
        fp->obj.fs = fs;             // fs object
        fp->obj.vol_id = fs->vol_id; // file volume id
        fp->flags = mode;            // file acess mode
        fp->err = 0;                 // file error code
        fp->sector = 0;              // file current sector
        fp->fptr = 0;                // reset file read/write offset

        memset(fp->buff, 0, sizeof(fp->buff)); // clear buffer

        // deal with seek
        if ((mode & FA_SEEKED) && fp->obj.size > 0) // seek to end of file
        {
            #if DEBUG_FS
            KPrint("fat: open with seeked\n");
            #endif
            fp->fptr = fp->obj.size;                      // offset to seek
            uint32_t size = fs->clust_size * SECTOR_SIZE; // clust size(eg:sectors)
            uint32_t clust = fp->obj.clust;
            uint32_t off = 0;
            // follow the clust chain
            for (off = fp->obj.size; res == FSTU_OK && off > size; off -= size)
            {
                clust = GetFat(&fp->obj, clust);
                if (clust <= 1)
                    res = FSTU_INT_ERR;
                if (clust == 0xFFFFFFFF)
                    res = FSTU_DISK_ERR;
            }
            fp->clust = clust;
            if (res == FSTU_OK && off % SECTOR_SIZE)
            {
                uint32_t sector = Clust2Sector(fs, clust);
                if (!sector)
                    res = FSTU_DISK_ERR;
                else
                    fp->sector = sector + (off / SECTOR_SIZE);
            }
            if (DiskRead(fs->pydrv, fp->buff, fp->sector, 1) != DSTU_OK)
                res = FSTU_DISK_ERR;
        }
    }

    if (res != FSTU_OK)
        fp->obj.fs = 0;
    return res;
}

// close file
file_status_t FileClose(file_obj_t *file)
{
    file_status_t res = FSTU_OK;
    fatfs_t *fs;

    res = FileSync(file);
    if (res != FSTU_OK)
    {
        KPrint("[fat] sync fs failed when close!\n");
        return res;
    }

    res = VertifyObj(&file->obj, &fs);
    if (res == FSTU_OK)
        file->obj.fs = 0;
    return res;
}

file_status_t FileOpenDir(dir_obj_t *dir, const char *path)
{
    file_status_t res;
    fatfs_t *fs;

    if (!dir)
        return FSTU_INVALID_OBJ;

    // mount volume
    res = MountVolume(path, &fs, 0);
    if (res == FSTU_OK)
    {
        dir->obj.fs = fs;
        res = FollowPath(dir, path); // follow path
        if (res == FSTU_OK)
        {
            if (!(dir->fn[NSFLAGS] & NS_NONAME)) // is not origin director itself
            {
                if (dir->obj.attr & ATT_FDIR) // is a sub director
                {
                    if (fs->fs_type == FS_EXFAT) // exFat
                    {
                        dir->obj.c_clust = dir->obj.clust;
                        dir->obj.c_size = ((uint32_t)dir->obj.size & 0xffffff00) | dir->obj.attr;
                        dir->obj.c_off = dir->block_off;
                        InitAllocInfo(fs, &dir->obj); // get object alloc
                    }
                    else
                    {
                        dir->obj.clust = LoadClust(fs, dir->dir); // load director start cluster for dir entries
                    }
                }
                else // is a file object
                {
                    KPrint("[fat] open object is a file!\n");
                    res = FSTU_NOT_FILE;
                }
            }

            if (res == FSTU_OK)
            {
                dir->obj.vol_id = fs->vol_id;
                res = DirSetIdx(dir, 0); // rewind director
            }
        }
        if (res == FSTU_NOT_FILE)
            res = FSTU_NO_PATH;
    }
    if (res != FSTU_OK)
        dir->obj.fs = 0; // invalid director object

    return res;
}

file_status_t FileCloseDir(dir_obj_t *dir)
{
    file_status_t res;
    fatfs_t *fs;

    res = VertifyObj(&dir->obj, &fs);
    if (res == FSTU_OK)
    {
        dir->obj.fs = 0;
    }
    return res;
}

file_status_t FileStatus(const char *path, file_info_t *fno)
{
    file_status_t res;
    dir_obj_t dir;

    res = MountVolume(path, &dir.obj.fs, 0);
    if (res == FSTU_OK)
    {
        res = FollowPath(&dir, path);

        if (res == FSTU_OK)
        {
            if (dir.fn[NSFLAGS] & NS_NONAME) // is origin director
                res = FSTU_INVALID_NAME;
            else // found object
            {
                if (fno)
                    GetFileInfo(&dir, fno); // get file info
            }
        }
    }
    return res;
}

file_status_t FileTruncate(file_obj_t *fp)
{
    file_status_t res;
    fatfs_t *fs;
    uint32_t nclust;

    res = VertifyObj(&fp->obj, &fs); // check obj
    if (res != FSTU_OK || (res = fp->err) != FSTU_OK)
        return res;
    if (!(fp->flags & FA_WRITE)) // readonly file no can truncate
        return FSTU_DENIED;

    if (fp->fptr < fp->obj.size) // file off no reached the eof
    {
        if (!(fp->fptr)) // set file size to zero
        {
            res = RemoveChain(&fp->obj, fp->obj.clust, 0);
        }
        else // truncate a part of file
        {
            nclust = GetFat(&fp->obj, fp->clust); // get file next clust
            if (nclust == 0xFFFFFFFF)
                res = FSTU_DISK_ERR;
            if (nclust == 1)
                res = FSTU_INT_ERR;
            if (res == FSTU_OK && nclust < fs->fat_entrys)
            {
                res = RemoveChain(&fp->obj, fp->obj.clust, 0); // truncate remind cluster from next
            }
        }
        fp->obj.size = fp->fptr; // update file size
        fp->flags |= FA_MODIFIED;
        if (res == FSTU_OK && (fp->flags & FA_DIRTY)) // buff dirty
        {
            if (DiskWrite(fs->pydrv, fp->buff, fp->sector, 1) != DSTU_OK) // write-back buff
            {
                res = FSTU_DISK_ERR;
            }
            else
            {
                fp->flags &= ~FA_DIRTY; // succeed,clear dirty flag
            }
        }
        if (res != FSTU_OK)
        {
            fp->err = res;
            return res;
        }
    }
    return res;
}

file_status_t FileUnlink(const char *path)
{
    file_status_t res;
    fatfs_t *fs;
    dir_obj_t dir, sdir;
    file_id_t obj;
    uint32_t dclust;

    KPrint("[fat] unlink path %s\n", path);

    // get logical drive
    res = MountVolume(path, &fs, FA_WRITE);
    if (res == FSTU_OK)
    {
        dir.obj.fs = fs;
        res = FollowPath(&dir, path); // follow the path
        if (res == FSTU_OK && (dir.fn[NSFLAGS] & NS_DOT))
        {
            res = FSTU_INVALID_NAME; // cannot remove dot entry
        }

        if (res == FSTU_OK)
        {
            if (dir.fn[NSFLAGS] & NS_NONAME)
            {
                res = FSTU_INVALID_NAME; // cannot remove origin entry
            }
            else
            {
                if (dir.obj.attr & ATT_RDONLY) // cannot remove R/O object
                {
                    res = FSTU_DENIED;
                }
            }
        }

        if (res == FSTU_OK)
        {
            // get director start cluster
            obj.fs = fs;
            if (fs->fs_type == FS_EXFAT)
            {
                InitAllocInfo(fs, &obj);
                dclust = obj.clust;
            }
            else
            {
                dclust = LoadClust(fs, dir.dir);
                KPrint("[fat] remove start cluster %d\n", dclust);
            }

            if (dir.obj.attr & ATT_FDIR) // is sub-director
            {
                KPrint("[fat] remove is dir\n");
                if (dclust == fs->cur_dir) // cannot remove current director
                {
                    res = FSTU_DENIED;
                }
                else
                {
                    // open sub-director
                    sdir.obj.fs = fs;
                    sdir.obj.clust = dclust;
                    if (fs->fs_type == FS_EXFAT)
                    {
                        sdir.obj.size = 0;
                        sdir.obj.status = obj.status;
                    }

                    res = DirSetIdx(&sdir, 0);
                    if (res == FSTU_OK)
                    {
                        // read dir and check whether is empty director
                        // cannot delete no empty director
                        res = DirRead(&sdir);
                        if (res == FSTU_OK) // no empty
                        {
                            KPrint("[fat] unlink: remove dir no empty\n");
                            res = FSTU_DENIED;
                        }
                        if (res == FSTU_NOT_FILE) // empty
                            res = FSTU_OK;
                    }
                }
            }
        }

        // delete director entry and remove chain alloc
        if (res == FSTU_OK)
        {
            res = DirRemove(&dir); // remove the director entry
            if (res == FSTU_OK)
            {
                // remove the cluster chain
                if (dclust != 0) // file no empty
                    res = RemoveChain(&obj, dclust, 0);
            }
            if (res == FSTU_OK)
                res = SyncFs(fs); // sync to fs
        }
    }
    return res;
}

file_status_t FileRead(
    file_obj_t *fp,
    void *buff,
    uint32_t btr,
    uint32_t *br)
{
    file_status_t res;
    fatfs_t *fs;
    DWORD clst;
    lba_t sect;
    uint32_t remain;
    uint32_t rcnt, cc, csect;
    BYTE *rbuff = (BYTE *)buff;

    *br = 0;                         /* Clear read byte counter */
    res = VertifyObj(&fp->obj, &fs); /* Check validity of the file object */
    if (res != FSTU_OK)              /* Check validity */
    {
        KPrint("[fat] file object err\n");
        fs->err = res;
        return res;
    }

    if (!(fp->flags & FA_READ)) /* Check access mode */
    {
        KPrint("[fat] file no read\n");
        fs->err = FSTU_DENIED;
        return FSTU_DENIED;
    }

    if (!fp->obj.size)
    {
        KPrint("[fat] read file empty\n");
        fs->err = FSTU_DENIED;
        return FSTU_DENIED;
    }

    remain = fp->obj.size - fp->fptr;
    if (btr > remain)
        btr = (uint32_t)remain; /* Truncate btr by remaining bytes */

    for (; btr; btr -= rcnt, *br += rcnt, rbuff += rcnt, fp->fptr += rcnt) /* Repeat until btr bytes read */
    {
        if (fp->fptr % fs->sector_size == 0)
        {                                                                          /* On the sector boundary? */
            csect = (uint32_t)(fp->fptr / fs->sector_size & (fs->clust_size - 1)); /* Sector offset in the cluster */
            if (csect == 0)
            { /* On the cluster boundary? */
                if (fp->fptr == 0)
                {                         /* On the top of the file? */
                    clst = fp->obj.clust; /* Follow cluster chain from the origin */
                }
                else
                { /* Middle or end of the file */

                    clst = GetFat(&fp->obj, fp->clust); /* Follow cluster chain on the FAT */
                }
                if (clst < 2)
                {
                    fs->err = FSTU_DISK_ERR;
                    return FSTU_DISK_ERR;
                }
                if (clst == 0xFFFFFFFF)
                {
                    fs->err = FSTU_DISK_ERR;
                    return FSTU_DISK_ERR;
                }
                fp->clust = clst; /* Update current cluster */
            }
            sect = Clust2Sector(fs, fp->clust); /* Get current sector */
            if (sect == 0)
            {
                fs->err = FSTU_INT_ERR;
                return FSTU_INT_ERR;
            }
            sect += csect;
            cc = btr / SECTOR_SIZE; /* When remaining bytes >= sector size, */
            if (cc > 0)
            { /* Read maximum contiguous sectors directly */
                if (csect + cc > fs->clust_size)
                { /* Clip at cluster boundary */
                    cc = fs->clust_size - csect;
                }
                if (DiskRead(fs->pydrv, rbuff, sect, cc) != DSTU_OK)
                {
                    fs->err = FSTU_DISK_ERR;
                    return FSTU_DISK_ERR;
                }

                if ((fp->flags & FA_DIRTY) && fp->sector - sect < cc)
                {
                    memcpy(rbuff + ((fp->sector - sect) * SECTOR_SIZE), fp->buff, SECTOR_SIZE);
                }

                rcnt = SECTOR_SIZE * cc; /* Number of bytes transferred */
                continue;
            }

            if (fp->sector != sect)
            { /* Load data sector if not in cache */

                if (fp->flags & FA_DIRTY)
                {
                    /* Write-back dirty sector cache */
                    if (DiskWrite(fs->pydrv, fp->buff, fp->sector, 1) != DSTU_OK)
                    {
                        fs->err = FSTU_DISK_ERR;
                        return FSTU_DISK_ERR;
                    }
                    fp->flags &= (BYTE)~FA_DIRTY;
                }
                if (DiskRead(fs->pydrv, fp->buff, sect, 1) != DSTU_OK) /* Fill sector cache */
                {
                    fs->err = FSTU_DISK_ERR;
                    return FSTU_DISK_ERR;
                }
            }
            fp->sector = sect;
        }
        rcnt = SECTOR_SIZE - (uint32_t)fp->fptr % SECTOR_SIZE; /* Number of bytes remains in the sector */
        if (rcnt > btr)
            rcnt = btr; /* Clip it by btr if needed */

        memcpy(rbuff, fp->buff + fp->fptr % SECTOR_SIZE, rcnt); /* Extract partial sector */
    }

    fs->err = FSTU_OK;
    return FSTU_OK;
}

file_status_t FileWrite(file_obj_t *fp, void *buff, uint32_t btw, uint32_t *bw)
{
    file_status_t res;
    fatfs_t *fs;
    DWORD clst;
    uint32_t sect;
    uint32_t wcnt, cc, csect;
    const BYTE *wbuff = (const BYTE *)buff;

    *bw = 0;                         /* Clear write byte counter */
    res = VertifyObj(&fp->obj, &fs); /* Check validity of the file object */
    if (res != FSTU_OK)
    {
        fs->err = res;
        return res;
    }
    if (!(fp->flags & FA_WRITE)) /* Check access mode */
    {
        KPrint("[fat] file no write\n");
        fs->err = FSTU_DENIED;
        return FSTU_DENIED;
    }

    /* Check fptr wrap-around (file size cannot reach 4 GiB at FAT volume) */
    if (fs->fs_type != FS_EXFAT && (DWORD)(fp->fptr + btw) < (DWORD)fp->fptr)
    {
        btw = (uint32_t)(0xFFFFFFFF - (DWORD)fp->fptr);
    }
    /* Repeat until all data written */
    for (; btw;
         btw -= wcnt, *bw += wcnt, wbuff += wcnt, fp->fptr += wcnt, fp->obj.size = (fp->fptr > fp->obj.size) ? fp->fptr : fp->obj.size)
    {
        if (fp->fptr % SECTOR_SIZE == 0)
        {                                                                      /* On the sector boundary? */
            csect = (uint32_t)(fp->fptr / SECTOR_SIZE) & (fs->clust_size - 1); /* Sector offset in the cluster */
            if (csect == 0)
            { /* On the cluster boundary? */
                if (fp->fptr == 0)
                {                         /* On the top of the file? */
                    clst = fp->obj.clust; /* Follow from the origin */
                    if (clst == 0)
                    {                                    /* If no cluster is allocated, */
                        clst = CreateChain(&fp->obj, 0); /* create a new cluster chain */
                    }
                }
                else
                { /* On the middle or end of the file */

                    clst = CreateChain(&fp->obj, fp->clust); /* Follow or stretch cluster chain on the FAT */
                }
                if (clst == 0)
                    break; /* Could not allocate a new cluster (disk full) */
                if (clst == 1)
                    return FSTU_INT_ERR;
                if (clst == 0xFFFFFFFF)
                    return FSTU_INT_ERR;
                fp->clust = clst; /* Update current cluster */
                if (fp->obj.clust == 0)
                    fp->obj.clust = clst; /* Set start cluster if the first write */
            }

            if (fp->flags & FA_DIRTY)
            { /* Write-back sector cache */
                if (DiskWrite(fs->pydrv, fp->buff, fp->sector, 1) != DSTU_OK)
                {
                    KPrint("[fat] write back err\n");
                    return FSTU_DISK_ERR;
                }
                fp->flags &= (BYTE)~FA_DIRTY;
            }

            sect = Clust2Sector(fs, fp->clust); /* Get current sector */
            if (sect == 0)
                return FSTU_DISK_ERR;
            sect += csect;
            cc = btw / SECTOR_SIZE; /* When remaining bytes >= sector size, */
            if (cc > 0)
            { /* Write maximum contiguous sectors directly */
                if (csect + cc > fs->clust_size)
                { /* Clip at cluster boundary */
                    cc = fs->clust_size - csect;
                }
                if (DiskWrite(fs->pydrv, wbuff, sect, cc) != DSTU_OK)
                {
                    KPrint("[fat] write disk err on %d\n", sect);
                    return FSTU_DISK_ERR;
                }

                if (fp->sector - sect < cc)
                { /* Refill sector cache if it gets invalidated by the direct write */
                    memcpy(fp->buff, wbuff + ((fp->sector - sect) * SECTOR_SIZE), SECTOR_SIZE);
                    fp->flags &= (BYTE)~FA_DIRTY;
                }

                wcnt = SECTOR_SIZE * cc; /* Number of bytes transferred */
                continue;
            }

            if (fp->sector != sect && /* Fill sector cache with file data */
                fp->fptr < fp->obj.size &&
                DiskRead(fs->pydrv, fp->buff, sect, 1) != DSTU_OK)
            {
                return FSTU_DISK_ERR;
            }

            fp->sector = sect;
        }
        wcnt = SECTOR_SIZE - (uint32_t)fp->fptr % SECTOR_SIZE; /* Number of bytes remains in the sector */
        if (wcnt > btw)
            wcnt = btw; /* Clip it by btw if needed */

        memcpy(fp->buff + fp->fptr % SECTOR_SIZE, wbuff, wcnt); /* Fit data to the sector */
        fp->flags |= FA_DIRTY;
    }
    fp->flags |= FA_MODIFIED; /* Set file change flag */

    return FSTU_OK;
}

file_status_t FileSync(file_obj_t *fp)
{
    file_status_t res;
    fatfs_t *fs;
    int time;
    uint8_t *dir;

    res = VertifyObj(&fp->obj, &fs);
    if (res == FSTU_OK)
    {
        if (fp->flags & FA_DIRTY)
        {
            KPrint("file sync %s\n",fp->buff);
            if (DiskWrite(fs->pydrv, fp->buff, fp->sector, 1) != DSTU_OK)
                return FSTU_DISK_ERR;
            fp->flags &= ~FA_DIRTY;
        }

        if (fp->flags & FA_MODIFIED)
        {
            time = GetFatTime(); // modified time
            if (fs->fs_type == FS_EXFAT)
            {
            }
            else
            {
                res = MoveWindows(fs, fp->dir_sec);
                if (res == FSTU_OK)
                {
                    dir = fp->dir_ptr;
                    ((dir_info_t *)dir)->attr |= ATT_ARCHIVE;           // set archieve flag indicate file had been changed
                    ((dir_info_t *)dir)->size = fp->obj.size;           // update file size
                    ((dir_info_t *)dir)->mdate = (time >> 16) & 0xffff; // update modified time
                    ((dir_info_t *)dir)->mtime = time & 0xffff;
                    ((dir_info_t *)dir)->atime = 0;
                    SetClust(fp->obj.fs, dir, fp->obj.clust); // update file allocation cluster
                    fs->wflags = 1;
                    res = SyncFs(fs); // restore to director
                    fp->flags &= ~FA_MODIFIED;
                }
            }
        }
    }
    return res;
}

file_status_t FileMkDir(const char *path)
{
    fatfs_t *fs = NULL;
    file_status_t res;
    dir_obj_t dir;
    file_id_t obj;
    int pclust, dclust;
    int time;

    // mount volume
    res = MountVolume(path, &fs, FA_WRITE);
    if (res == FSTU_OK)
    {
        dir.obj.fs = fs;
        res = FollowPath(&dir, path); // follow path
        if (res == FSTU_OK)
            res = FSTU_EXIST;                                   // exist
        if (res == FSTU_NOT_FILE && (dir.fn[NSFLAGS] & NS_DOT)) // invalid name
            res = FSTU_INVALID_NAME;
        if (res == FSTU_NOT_FILE)
        {
#if DEBUG_FS
            KPrint("[fat] try to create new dir!\n");
#endif
            obj.fs = fs;                   // new object id to create
            dclust = CreateChain(&obj, 0); // alloc a cluster for director
#if DEBUG_FS
            KPrint("[fat] alloc for new dir clust %d\n", dclust);
#endif
            res = FSTU_OK;
            if (dclust == 0)
                res = FSTU_DENIED;
            if (dclust == 1)
                res = FSTU_INT_ERR;
            if (dclust == 0xFFFFFFFF)
                res = FSTU_DISK_ERR;
            time = GetFatTime();
            if (dclust != 0)
            {
                res = DirClear(fs, dclust); // clear new table
                if (res == FSTU_OK)
                {
                    if (fs->fs_type != FS_EXFAT)
                    {
                        // clear buffer old data
                        memset(fs->win, 0, SECTOR_SIZE);
                        memset(((dir_info_t *)(fs->win))->name, ' ', 11); // create  '.' entry
                        ((dir_info_t *)(fs->win))->name[0] = '.';
                        ((dir_info_t *)(fs->win))->attr = ATT_FDIR;
                        ((dir_info_t *)(fs->win))->mtime = time;
                        SetClust(fs, fs->win, dclust);
                        memcpy(fs->win + DIR_TERN_SIZE, fs->win, DIR_TERN_SIZE); // create '..' entry
                        ((dir_info_t *)(fs->win + DIR_TERN_SIZE))->name[1] = '.';
                        pclust = dir.obj.clust;
                        SetClust(fs, fs->win + DIR_TERN_SIZE, pclust);
                        fs->wflags = 1;
                    }
                    res = DirRegister(&dir); // register director object
                }
            }
            if (res == FSTU_OK) // dir register success
            {
                if (fs->fs_type == FS_EXFAT) // init director entry
                {
                    ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_primary.mtime = time;                         // create time
                    ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_primary.ctime = time;                         // access time
                    ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.cluster = dclust;                    // director table start clust
                    ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.size = fs->clust_size * SECTOR_SIZE; // director size(one cluster size)
                    ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.lenght = fs->clust_size * SECTOR_SIZE;
                    ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.flags = 3;      // init object flags
                    ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_primary.attr = ATT_FDIR; // director
                    res = StoreXdir(&dir);
                }
                else
                {
                    ((dir_info_t *)(dir.dir))->mtime = time;    // create time
                    ((dir_info_t *)(dir.dir))->atime = time;    // acess time
                    SetClust(fs, dir.dir, dclust);              // table start clust
                    ((dir_info_t *)(dir.dir))->attr = ATT_FDIR; // attribute
                    fs->wflags = 1;
                }
                if (res == FSTU_OK)
                {
                    res = SyncFs(fs);
                }
            }
            else
            {
                RemoveChain(&obj, dclust, 0); // counld register,remove allocation cluster chain
            }
        }
    }
    return res;
}

file_status_t FileChDriver(const char *path)
{
    int vol;
    vol = GetVolNumber(path);
    if (vol < 0)
        return FSTU_INVALID_DRIVER;

    cur_vol = vol; // set volume to cur_vol

    return FSTU_OK;
}

file_status_t FileRename(const char *path_old, const char *path_new)
{
    file_status_t res;
    dir_obj_t dj, di;
    fatfs_t *fs;
    lba_t sector;
    uint8_t buff[DIR_TERN_SIZE * 2], *dir;

    res = MountVolume(path_old, &fs, FA_WRITE); // get logical driver of old object
    if (res == FSTU_OK)
    {
        dj.obj.fs = fs;
        res = FollowPath(&dj, path_old); // check old object
        if (res == FSTU_OK && (dj.fn[NSFLAGS] & (NS_DOT | NS_NONAME)))
            return FSTU_INVALID_OBJ;

        if (res == FSTU_OK) // rename object
        {
            if (fs->fs_type == FS_EXFAT) // exFAT volume
            {
                uint8_t nf, nn;
                uint16_t nh;

                memcpy(buff, fs->dir_buff, DIR_TERN_SIZE * 2); // save old object entries
                memcpy(&di, &dj, sizeof(dj));                  // copy old object
                res = FollowPath(&di, path_new);               // make new object name no use
                if (res != FSTU_OK)                            // new object exist
                {
                    res = (di.obj.clust == dj.obj.clust && di.dir == dj.dir) ? FSTU_NOT_FILE : FSTU_EXIST;
                }
                if (res == FSTU_NOT_FILE) // new object no exist
                {
                    res = DirRegister(&di); // register new entry
                    if (res == FSTU_OK)
                    {
                        nf = ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_primary.num_dir;
                        nn = ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.name_len;
                        nh = ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.name_chksum;
                        memcpy(fs->dir_buff, buff, DIR_TERN_SIZE * 2); // restore old entries
                        ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_primary.num_dir = nf;
                        ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.name_len = nn;
                        ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.name_chksum = nh;
                        if (!(((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_primary.attr & ATT_FDIR))
                            ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_primary.attr |= ATT_ARCHIVE; // set archive attr if it is file
                        res = StoreXdir(&di);
                    }
                }
            }
            else
            {
                // FAT/FAT32 volume
                memcpy(buff, dj.dir, DIR_TERN_SIZE); // save director entry of old obejct
                memcpy(&di, &dj, sizeof(dir_obj_t)); // duplicate director object
                res = FollowPath(&di, path_new);     // make sure new object name no exist
                if (res == FSTU_OK)
                {
                    res = (((di.obj.clust == dj.obj.clust) && (di.dir == dj.dir)) ? FSTU_NOT_FILE : FSTU_EXIST);
                }

                if (res == FSTU_NOT_FILE)
                {
                    res = DirRegister(&di); // register new entry
                    if (res == FSTU_OK)
                    {
                        dir = di.dir;
                        memcpy(dir + 13, buff + 13, DIR_TERN_SIZE - 1);
                        ((dir_info_t *)dir)->attr = ((dir_info_t *)buff)->attr;
                        if (!(((dir_info_t *)dir)->attr & ATT_FDIR)) // set archieve
                            ((dir_info_t *)dir)->attr |= ATT_ARCHIVE;
                        fs->wflags = 1;
                        if ((((dir_info_t *)dir)->attr & ATT_FDIR) && di.obj.clust != 0) // update entry in the sub-director if needs
                        {
                            sector = Clust2Sector(fs, LoadClust(fs, dir));
                            if (!sector)
                                res = FSTU_INT_ERR;
                            else
                            {
                                res = MoveWindows(fs, sector);
                                dir = fs->win + DIR_TERN_SIZE * 1; // poing to .. entry
                                if (res == FSTU_OK && dir[1] == '.')
                                {
                                    SetClust(fs, dir, di.obj.clust);
                                    fs->wflags = 1;
                                }
                            }
                        }
                    }
                }
            }

            if (res == FSTU_OK)
            {
                #if DEBUG_FS
                KPrint("[fat] rename: remove old entry!\n");
                #endif 
                res = DirRemove(&dj); // remove old entry
                if (res == FSTU_OK)
                    res = SyncFs(fs);
            }
        }
    }
    return res;
}

file_status_t FileChDir(const char *path)
{
    file_status_t res;
    fatfs_t *fs;
    dir_obj_t dir;

    res = MountVolume(path, &fs, 0); // mount volume
    if (res == FSTU_OK)
    {
        dir.obj.fs = fs;
        res = FollowPath(&dir, path); // follow path

        if (res == FSTU_OK)
        {
            if (dir.fn[NSFLAGS] & NS_NONAME) // is start director itself
            {
                fs->cur_dir = dir.obj.clust;
                if (fs->fs_type == FS_EXFAT)
                {
                    fs->cd_clust = dir.obj.c_clust;
                    fs->cd_off = dir.obj.c_off;
                    fs->cd_size = dir.obj.c_size;
                }
            }
            else
            {
                if (dir.obj.attr & ATT_FDIR) // is sub-director
                {
                    if (fs->fs_type == FS_EXFAT)
                    {
                        fs->cur_dir = ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_seconday.cluster; // sub director clust
                        fs->cd_clust = dir.obj.clust;                                                  // contain director start cluster
                        fs->cd_size = dir.obj.size;                                                    // contain director size
                        fs->cd_off = dir.block_off;                                                    // offset of contain director
                    }
                    else
                    {
                        fs->cur_dir = LoadClust(fs, dir.dir); // load clust from director entry
                    }
                }
                else
                {
                    res = FSTU_NO_PATH; // reached but no dir
                }
            }
        }
        if (res == FSTU_NOT_FILE)
            res = FSTU_NO_PATH;
    }
    return res;
}

file_status_t FileReadDir(dir_obj_t *dir, file_info_t *fno)
{
    file_status_t res;
    fatfs_t *fs;

    res = VertifyObj(&dir->obj, &fs); // check object
    if (res == FSTU_OK)
    {
        if (!fno)
        {
            res = DirSetIdx(dir, 0); // rewind the director
            #if DEBUG_FS
            KPrint("[fat] rewind dir ok!\n");
            #endif 
        }
        else
        {
            res = DirRead(dir); // read an item
            if (res == FSTU_NOT_FILE)
                return FSTU_NOT_FILE;
            if (res == FSTU_OK)
            {
                GetFileInfo(dir, fno); // get the object information
                res = DirNext(dir, 0); // next director index
                if (res == FSTU_NOT_FILE)
                    res = FSTU_OK; // next is end of dir,return ok
            }
        }
    }
    return res;
}

file_status_t FileGetCwd(uint8_t *buff, uint32_t len)
{
    file_status_t res;
    uint8_t *p = buff;
    uint32_t clust;
    fatfs_t *fs;
    dir_obj_t dir;
    file_info_t info;
    uint32_t i, n;

    // get logical driver
    memset(buff, 0, len);
    res = MountVolume((const char *)buff, &fs, 0); // mount volume
    if (res == FSTU_OK)
    {
        dir.obj.fs = fs;

        // follow parent director and create path
        i = len;
        if (fs->fs_type != FS_EXFAT)
        {
            dir.obj.clust = fs->cur_dir;         // follow upper directory from current director
            while ((clust = dir.obj.clust) != 0) // repeat until current director no is sub-director
            {
                res = DirSetIdx(&dir, 1 * DIR_TERN_SIZE); // get parent director
                if (res != FSTU_OK)
                    break;
                res = MoveWindows(fs, dir.sector);
                if (res != FSTU_OK)
                    break;
                dir.obj.clust = LoadClust(fs, dir.dir); // get parent director cluster
                res = DirSetIdx(&dir, 0);
                if (res != FSTU_OK)
                    break;
                do // find the entry links to the child director
                {
                    res = DirRead(&dir);
                    if (res != FSTU_OK)
                        break;
                    if (clust == LoadClust(fs, dir.dir)) // find the entry
                        break;
                    res = DirNext(&dir, 0); // next dir
                } while (res == FSTU_OK);
                if (res == FSTU_NOT_FILE) // child director no found
                    res = FSTU_INT_ERR;
                if (res != FSTU_OK)
                    break;
                GetFileInfo(&dir, &info);       // get director name
                for (n = 0; info.fname[n]; n++) // get filename len
                    ;
                if (i < n + 1)
                    res = FSTU_BUFFER_ERROR; // buffer too small
                while (n)
                    buff[--i] = info.fname[--i]; // copy name to buff(follow last to front)
                buff[--i] = '/';
            }
        }
        if (res == FSTU_OK)
        {
            if (i == len) // is root-directory
            {
                buff[--i] = '/';
            }
            while (i < len) // coph path string to head
                *p++ = buff[i++];
        }
    }
    *p = 0;
    return res;
}

file_status_t FileLSeek(file_obj_t *fp, uint32_t off)
{
    file_status_t res;
    fatfs_t *fs;
    uint32_t clust, clsize;
    lba_t sector;
    uint64_t ifptr;

    res = VertifyObj(&fp->obj, &fs);
    if (res != FSTU_OK)
    {
        KPrint("[fat] lseek: obj invalid!\n");
        res = fp->err;
        return res;
    }

    if (res != FSTU_OK && fs->fs_type == FS_EXFAT)
        res = FillLastFrag(&fp->obj, fp->clust, 0xffffffff);

    // seek reach to maximum size of file
    if (fs->fs_type != FS_EXFAT && off >= 0xffffffff)
        off = 0xffffffff;

    if (off >= fp->obj.size && (!(fp->flags & FA_WRITE)))
    {
        KPrint("[fat] off above file size and this file no open with FA_WRITE!\n");
        off = fp->obj.size; // off above limit
    }

    ifptr = fp->fptr;
    fp->fptr = sector = 0;
    if (off == 0) //seek to file start position
    {
        fp->fptr = 0;

        clust = fp->obj.clust;
        if (clust == 0) //no file size,first write file,return FSTU_OK
        {
            fp->clust=0;
            sector = 0;
            fp->sector=0;
            return FSTU_OK;
        }
        if (clust == 0xffffffff)
            return FSTU_INVALID_OBJ;
        if (clust < 2)
            return FSTU_INVALID_OBJ;
        sector = Clust2Sector(fs, clust);   //otherwsise,postion to sectors
    }

    if (off > 0)
    {
        clsize = fs->clust_size * SECTOR_SIZE;
        if (ifptr > 0 && (off - 1) / clsize >= do_div64((ifptr - 1), clsize)) // when seek to same cluster or follow cluster
        {
            fp->fptr = (ifptr - 1) & ~(clsize - 1);     //from current clust offset start
            off -= fp->fptr;    //remove current offset
            clust = fp->clust; //seek start from current clust
        }
        else
        {
            // when seek to back cluster
            clust = fp->obj.clust; // start for first clust
            
            // first write file
            if (!clust)
            {
                clust = CreateChain(&fp->obj, 0); //create new clust
                if (clust == 1)
                {
                    fs->err = FSTU_INT_ERR;
                    return FSTU_INT_ERR;
                }
                if (clust == 0xFFFFFFFF)
                {
                    fs->err = FSTU_DISK_ERR;
                    return FSTU_DISK_ERR;
                }
                fp->obj.clust = clust;
            }
            fp->clust = clust;
        }

        if (clust != 0)
        {
            while (off > clsize) // follow cluster(move unit: clustsz,result just is move to targe clust stop)
            {
                off -= clsize;
                fp->fptr += clsize;

                if (fp->flags & FA_WRITE) // if can write
                {
                    if (fp->fptr >= fp->obj.size) // correct object size
                    {
                        fp->obj.size = fp->fptr;
                        fp->flags |= FA_MODIFIED;

                        clust = CreateChain(&fp->obj, clust); // follow chain
                        if (!clust)
                        {   
                            off = 0; // new clust create failed,return
                            break;
                        }
                    }
                }
                else
                {
                    if (fp->fptr >= fp->obj.size) // no write and above file size
                    {
                        KPrint("[fat] seek err! above file size and no expand!\n");
                        return FSTU_INT_ERR;
                    }
                    clust = GetFat(&fp->obj, clust); // follow cluster chain if not in write mode
                }

                if (clust == 0xFFFFFFFF)
                {
                    fs->err = FSTU_DISK_ERR;
                    return FSTU_DISK_ERR;
                }
                if (clust <= 1 || clust >= fs->fat_entrys)
                {
                    fs->err = FSTU_INT_ERR;
                    return FSTU_INT_ERR;
                }
                fp->clust = clust;
            }
            fp->fptr += off; // set current offset
            if (off%SECTOR_SIZE>0) //less off within sector 
            {
                sector = Clust2Sector(fs, clust);
                if (!sector)
                {
                    fs->err = FSTU_INT_ERR;
                    return FSTU_INT_ERR;
                }
                sector += off / SECTOR_SIZE; // current sector
            }
        }

        // set file flags when file size extended
        if (fp->fptr >= fp->obj.size)
        {
            fp->obj.size = fp->fptr;
            fp->flags |= FA_MODIFIED;
        }
    }

    // update cache
    if ((fp->fptr >= 0) && (sector != fp->sector))
    {
        if (fp->flags & FA_DIRTY)
        {
            if (DiskWrite(fs->pydrv, fp->buff, fp->sector, 1) != DSTU_OK) // write-back dirty data
            {
                fs->err = FSTU_DISK_ERR;
                return FSTU_DISK_ERR;
            }
            fp->flags &= ~FA_DIRTY;
        }
        if (DiskRead(fs->pydrv, fp->buff, sector, 1) != DSTU_OK)
        {
            fs->err = FSTU_DISK_ERR;
            return FSTU_DISK_ERR;
        }
        fp->sector = sector;
    }
    return FSTU_OK;
}

file_status_t FileUtime(const char *path, file_info_t *finfo)
{
    fatfs_t *fs;
    file_status_t res;
    dir_obj_t dir;

    res = MountVolume(path, &fs, FA_WRITE); // mount volume
    if (res != FSTU_OK)
        return res;

    res = FollowPath(&dir, path);                                  // follow path
    if (res == FSTU_OK && dir.fn[NSFLAGS] == (NS_DOT | NS_NONAME)) // invalid name
        return FSTU_INVALID_NAME;

    if (res == FSTU_OK)
    {
#if FS_EXFAT
        if (fs->fs_type == FS_EXFAT)
        {
            ((xdir_info_t *)(fs->dir_buff))->xdir_dir.xdir_primary.ctime = (uint32_t)((uint32_t)finfo->time << 16 | finfo->date);
            res = StoreXdir(&dir);
        }
        else
#endif
        {
            ((dir_info_t *)(dir.dir))->ctime = ((uint32_t)finfo->time << 16 | finfo->date);
            fs->wflags = 1;
        }

        if (res == FSTU_OK)
            SyncFs(fs);
    }
    return res;
}

file_status_t FileMount(fatfs_t *fs, const char *path, uint8_t op)
{
    int vol;
    fatfs_t *cfs;
    file_status_t res;

    vol = GetVolNumber(path);
    if (vol < 0)
        return FSTU_INVALID_DRIVER;

    // remount
    cfs = fatfs[vol]; // get fs point
    if (cfs)
    {
        cfs->fs_type = 0;
    }

    // init new fs object fstype
    if (fs)
    {
        fs->fs_type = 0;
    }
    fatfs[vol] = fs;

    if (op == 0) // unmount
    {
        KPrint("[fat] mount op=0, no mount!\n");
        return FSTU_OK;
    }
    res = MountVolume(path, &fs, 0);
    return res;
}

file_status_t FileMkFs(const char *path, mkfs_parm_t *op, void *work, uint32_t len)
{
    file_status_t res;
    static const uint16_t cst[] = {1, 4, 16, 64, 256, 512, 0}; /* Cluster size boundary for FAT volume (4Ks unit) */
    static const uint16_t cst32[] = {1, 2, 4, 8, 16, 32, 0};   /* Cluster size boundary for FAT32 volume (128Ks unit) */
    mkfs_parm_t defop = {FM_ANY, 0, 0, 0, 0};
    uint8_t vol;
    uint8_t pdrv, part;
    uint32_t block_size, sector_size, pclust_size, clust_size, clust_num;
    uint32_t reserved_size, dir_size, fat_num, rootdir_num;
    uint32_t vol_start, vol_size, fat_start, fat_size, data_start, data_size;
    uint8_t fsop, fstype;
    uint8_t *buff;
    uint32_t entry_num, table_lba, off, i, n;
    uint32_t buff_size; // work buffer size
    uint8_t *pte;
    disk_status_t dstu;
    lba_t lba[2];
    uint64_t bitmap_size;

    #if DEBUG_FS
    KPrint("[fat] mkfs on driver %s\n", path);
    #endif
    vol = GetVolNumber(path); // get driver
    if (vol < 0)
        return FSTU_INVALID_DRIVER;
    KPrint("[fat] mkfs on vol %d\n",vol);
    // reset volume fs type
    if (fatfs[vol])
    {
        fatfs[vol]->fs_type = 0;
    }

    pdrv = LD2PD(vol); // physical driver
    part = LD2PT(vol); // partition
    KPrint("[fat] mkfs on vol %d pdrv %d part %d\n", vol, pdrv, part);

    if (!op)
        op = &defop; // if not assign op just to use defalut parame

    // get disk status
    dstu = DiskOpen(vol);
    if (dstu & DSTU_NOINIT)
        return FSTU_NOT_READY;
    if (dstu & DSTU_PROTECT)
        return FSTU_WRITE_PROTECT;
    block_size = op->align;
    // try get block size from disk
    if (block_size == 0 && DiskIoCtl(pdrv, GET_BLOCK_SIZE, &block_size) != DSTU_OK)
        block_size = 1;
    if (block_size == 0 || block_size > 0x8000 || (block_size & (block_size - 1)))
        block_size = 1;
    sector_size = SECTOR_SIZE;

    // get mkfs parme
    fsop = op->fmt & (FM_ANY | FM_SFD);
    fat_num = (op->fat_num >= 1 && op->fat_num <= 2) ? op->fat_num : 1;
    rootdir_num = (op->rootdir_num >= 1 && op->rootdir_num <= 32768 && !(op->rootdir_num % (SECTOR_SIZE / DIR_TERN_SIZE))) ? op->rootdir_num : 512;
    clust_size = (op->clust_size < 0x1000000 && !(op->clust_size & (op->clust_size - 1))) ? op->clust_size : 0;
    clust_size /= sector_size; // byte->sector

    // get working buffer
    buff_size = len / SECTOR_SIZE;
    if (!buff_size)
        return FSTU_BUFFER_ERROR;
    buff = work;
    if (!buff)
        return FSTU_BUFFER_ERROR;

    // determine where the volume to be located
    vol_start = vol_size = 0;

    KPrint("[fat] get vol size from device when mkfs\n");
    // get volume size from physical driver
    if (DiskIoCtl(vol, GET_SECTOR_COUNT, &vol_size) != DSTU_OK)
        return FSTU_DISK_ERR;

    if (!fsop & FM_SFD) // to be partition
    {
        // create single partition on the driver
        if (vol_size >= FS_GPT_MIN)
        {
            fsop |= 0x80; // partition in GPT
            vol_start = GPT_ALIGN / SECTOR_SIZE;
            vol_size -= vol_start + GPT_ITEMS * GPT_ENTRY_SIZE / SECTOR_SIZE + 1;
        }
        else
        {
            // partition in MBR
            if (vol_size > SEC_PER_TRACK)
            {
                vol_start = SEC_PER_TRACK;
                vol_size -= vol_start;
            }
        }
    }

    KPrint("[fat] mkfs vol start %d vol size %d\n", vol_start, vol_size);

    if (vol_size < 128) // check if volume size < 128
    {
        KPrint("[fat] mkfs vol size too small!\n");
        return FSTU_MKFS_ABORT;
    }
    // volume size too big
    if (vol_size >= 0x100000000)
    {
        KPrint("[mkfs] vol size too long\n");
        return FSTU_MKFS_ABORT;
    }

    // create FATs volume
    // determine the FAT type
    if (fsop & FM_EXFAT) // EXFAT
    {
        if ((fsop & FM_ANY) == FM_EXFAT || vol_size > 0x4000000 || clust_size > 128) // exbpb,vol >= 64M or clust > 128
        {
            fstype = FS_EXFAT;
        }
    }

    if (clust_size > 128) // invalid clust size
        clust_size = 128;

    if (fsop & FM_FAT32) // possible FAT32
    {
        if (!(fsop & FM_FAT)) // no FAT
        {
            fstype = FS_FAT32;
        }
    }
    else
    {
        if (!(fsop & FM_FAT))
        {
            KPrint("[fat] mkfs no valid arg\n");
            return FSTU_INVALID_PARMETER;
        }
        else
        {
            fstype = FS_FAT16;
        }
    }
    KPrint("[fat] mkfs type %d\n", fstype);

    // create EXFAT volume
    if (fstype == FM_EXFAT)
    {
        uint64_t bitmap_size, case_size, sum, clust, tbl[3];
        uint64_t sector, sectors;
        uint16_t ch, si;
        uint64_t i, j, b, st;
        uint64_t bits;

        if (vol_size < 0x1000) // vol too small
            return FSTU_MKFS_ABORT;

        if (!clust_size) // auto clust size
        {
            clust_size = 8;
            if (vol_size >= 0x8000) // clust size >=512K sector
                clust_size = 64;
            if (vol_size >= 0x40000000) // clust size >= 64k sector
                clust_size = 256;
        }

        fat_start = vol_start + 32;                                                             // fat start
        fat_size = (uint64_t)((vol_size / clust_size + 2) * 4 + sector_size - 1) / sector_size; // number of FAT sectors
        data_start = (fat_start + fat_size + block_size - 1) / block_size;                      // data start aligned to block bound
        if (data_start - vol_start >= vol_size / 2)                                             // volume too small
            return FSTU_MKFS_ABORT;
        clust_num = (uint64_t)(vol_size - (data_start - vol_start)) / clust_size; // number of clusters
        if (clust_num < 16)                                                       // too small cluster
            return FSTU_MKFS_ABORT;
        if (clust_num > EXFAT_CLUST_MAX) // cluster too many
            return FSTU_MKFS_ABORT;

        bitmap_size = (clust_num + 7) / 8;                                                            // size of allocation bitmap
        tbl[0] = (uint32_t)(bitmap_size + clust_size * SECTOR_SIZE - 1) / (clust_size * SECTOR_SIZE); // number of alloc bitmap cluster

        // create a up-case table
        sector = data_start + clust_size * tbl[0]; // get up-case table start sectors
        sum = 0;
        st = 0;
        si = 0;
        i = 0;
        j = 0;
        case_size = 0;
        do
        {
            switch (st)
            {
            case 0:
                ch = (uint16_t)FsWtoupper(si); // get an up-case char
                if (ch != si)
                {
                    si++; // store up-case char if exist
                    break;
                }
                for (j = 1; (uint16_t)(si + j) && (uint16_t)(si + j) == FsWtoupper((uint16_t)(si + j)); j++) // get run length of no-case block
                    ;
                if (j >= 128)
                {
                    ch = 0xffff; // compress the no-case block if run>=128
                    st = 2;
                    break;
                }
                st = 1; // no compress short run
            case 1:
                ch = si++; // fill the short run
                if (!(--j))
                    st = 0;
                break;

            default:
                // number of chars skip
                ch = (uint16_t)j;
                si += (uint16_t)j;
            }
            sum = XSum32(buff[i + 0] = ch, sum); // put it int write buffer
            sum = XSum32(buff[i + 1] = (ch >> 8), sum);
            i += 2;
            case_size += 2;
            if (si == 0 || i == buff_size * SECTOR_SIZE) // write buffer data when full
            {
                n = (i + SECTOR_SIZE - 1) / SECTOR_SIZE;
                if (DiskWrite(part, buff, sector, n) != DSTU_OK)
                    return FSTU_DISK_ERR;
                sector += n;
                i = 0;
            }
        } while (si);

        tbl[1] = (uint32_t)(case_size + clust_size * SECTOR_SIZE) / (clust_size * SECTOR_SIZE); // number of up-case cluster
        tbl[2] = 1;                                                                             // number of rootdir cluster

        // init alloc bitmap(a bit instand of a cluster)
        sector = data_start;                                     // start of sector for bitmap
        sectors = (bitmap_size + SECTOR_SIZE - 1) / SECTOR_SIZE; // number of sectors in bitmap
        bits = tbl[0] + tbl[1] + tbl[2];                         // number of clusters in-use by system
        do
        {
            memset(buff, 0, buff_size * SECTOR_SIZE);
            for (i = 0; bits >= 8 && i < buff_size * SECTOR_SIZE; buff[i]++ == 0xff, bits -= 8) // as bytes
                ;
            for (b = 1; bits && i < buff_size * SECTOR_SIZE; buff[i] |= b, b <<= 1, bits--) // as bit
                ;
            n = (sectors > buff_size) ? buff_size : sectors; // write buffer
            if (DiskWrite(vol, buff, sector, n) != DSTU_OK)
                return FSTU_OK;
            sector += n;
            sectors -= n;
        } while (sectors);

        // init fat
        sector = fat_start;
        sectors = fat_size;
        j = bits = clust = 0;
        do
        {
            memset(buff, 0, buff_size * SECTOR_SIZE);
            i = 0;
            if (!clust) // init fat[0] and fat[1]
            {
                *(uint32_t *)(buff + i) = 0xFFFFFFF8;
                i += 4;
                clust++;
                *(uint32_t *)(buff + i) = 0xFFFFFFFF;
                i += 4;
                clust++;
            }
            do
            {
                // create chain for up-case,bitmap,root dir
                while (bits && i < buff_size * SECTOR_SIZE)
                {
                    *(uint32_t *)(buff + i) = (bits > 1) ? clust + 1 : 0xffffffff; // create a chain
                    bits--;
                    i += 4;
                }
                if (!bits && j < 3) // next china
                    bits = tbl[j++];
            } while (bits && i < buff_size * SECTOR_SIZE);
        } while (sectors);

        // init root director
        memset(buff, 0, buff_size * SECTOR_SIZE);
        buff[0 * DIR_TERN_SIZE + 0] = EXFAT_VOLLABEL;                                           // volume label entry
        buff[1 * DIR_TERN_SIZE + 0] = EXFAT_BITMAP;                                             // bitmap entry
        ((xdir_info_t *)(buff + 1 * DIR_TERN_SIZE))->xdir_dir.xdir_bitmap.cluster = 2;          // bitmap start cluster
        ((xdir_info_t *)(buff + 1 * DIR_TERN_SIZE))->xdir_dir.xdir_bitmap.length = bitmap_size; // bitmap size
        buff[2 * DIR_TERN_SIZE + 0] = EXFAT_UPCASE;                                             // up-case table entry
        ((xdir_info_t *)(buff + 2 * DIR_TERN_SIZE))->xdir_dir.xdir_upper.chksum = sum;          // up-case sum
        ((xdir_info_t *)(buff + 2 * DIR_TERN_SIZE))->xdir_dir.xdir_upper.cluster = 2 + tbl[0];  // up-case cluster
        ((xdir_info_t *)(buff + 2 * DIR_TERN_SIZE))->xdir_dir.xdir_upper.length = case_size;    // up-case size
        sector = data_start + clust_size * (tbl[0] + tbl[1]);                                   // start of root director
        sectors = clust_size;
        do
        {
            // fill root driector sectors
            n = (sectors > buff_size) ? buff_size : sectors;
            if (DiskWrite(part, buff, sector, n) != DSTU_OK)
                return FSTU_DISK_ERR;
            memset(buff, 0, sector_size);
            sector += n;
            sectors -= n;
        } while (sectors);

        // create a exFAT vbr
        sector = vol_start;
        for (n = 0; n < 2; n++)
        {
            // master record
            memset(buff, 0, sector_size);
            memcpy(((xbpb_t *)buff)->jmp, "\xEB\x76\x98"
                                          "EXFAT   ",
                   11);                                                                                        // boot jump coe
            ((xbpb_t *)buff)->start_sector = vol_start;                                                        // volume start
            ((xbpb_t *)buff)->total_size = vol_size;                                                           // volume size
            ((xbpb_t *)buff)->rootdir_clust = 2 + tbl[0] + tbl[1];                                             // root director cluster
            ((xbpb_t *)buff)->fat_sector = fat_start - vol_start;                                              // fat offset
            ((xbpb_t *)buff)->fat_size = fat_size;                                                             // fat size
            ((xbpb_t *)buff)->fat_count = 1;                                                                   // fat number
            ((xbpb_t *)buff)->clust_sector = (data_start - vol_start);                                         // data offset
            ((xbpb_t *)buff)->clust_count = clust_num;                                                         // number of clusters
            ((xbpb_t *)buff)->vol_id = GetFatTime();                                                           // volume serial id
            ((xbpb_t *)buff)->vol_ver = 0x100;                                                                 // version
            for (((xbpb_t *)buff)->sector_size = 0, i = sector_size; i >>= 1; ((xbpb_t *)buff)->sector_size++) // byte per sector
                ;
            for (((xbpb_t *)buff)->clust_size = 0, i = clust_size; i >>= 1; ((xbpb_t *)buff)->clust_sector++) // sector per cluster
                ;
            ((xbpb_t *)buff)->driver_flags = 0x80;               // driver flags
            *(uint16_t *)(((xbpb_t *)buff)->boot_code) = 0xFFEB; // boot code
            ((xbpb_t *)buff)->magic = 0xaa55;                    // signature
            for (i = sum = 0; i < sector_size; i++)              // checksum
            {
                if (i != XBPB_VOLSTATUS && i != XBPB_VOLSTATUS + 1 && i != XBPB_USING)
                    sum = XSum32(buff[i], sum);
            }
            if (DiskWrite(vol, buff, sector++, 1) != DSTU_OK)
                return FSTU_DISK_ERR;

            // extended boot record
            memset(buff, 0, sector_size);
            *(uint16_t *)(buff + sector_size - 2) = 0xaa55; // signature
            for (j = 1; j < 9; j++)
            {
                for (i = 0; i < sector_size; sum = XSum32(buff[i++], sum)) // checksum
                    ;
                if (DiskWrite(vol, buff, sector++, 1) != DSTU_OK)
                    return FSTU_DISK_ERR;
            }

            // OEM record
            memset(buff, 0, sector_size);
            for (; j < 11; j++)
            {
                for (i = 0; i < sector_size; sum = XSum32(buff[i++], sum))
                    ; // checksum
                if (DiskWrite(vol, buff, sector++, 1) != DSTU_OK)
                    return FSTU_DISK_ERR;
            }

            // sum record
            for (i = 0; i < sector_size; i += 4)
                *(uint32_t *)(buff + i) = sum; // fill sum
            if (DiskWrite(vol, buff, sector++, 1) != DSTU_OK)
                return FSTU_DISK_ERR;
        }
    }
    else
    {
        while (1)
        {
            // create a FAT/FAT32 volume
            pclust_size = clust_size;
            // determine number of clusters and FAT sub-type
            if (fstype == FS_FAT32)
            {
                // FAT32 volume
                if (!pclust_size)
                {
                    n = vol_size / 0x20000; // volume size is unit of 128KS
                    for (i = 0, pclust_size = 1; cst32[i] && cst32[i] <= n; i++, pclust_size <<= 1)
                        ;
                }
                clust_num = vol_size / pclust_size;
                fat_size = (clust_num * 4 + 8 + (SECTOR_SIZE - 1)) / SECTOR_SIZE;
                reserved_size = 32;
                dir_size = 0; // dynamic director
                if (clust_num <= FAT16_CLUST_MAX || clust_num >= FAT32_CLUST_MAX)
                {
                    KPrint("[fat] mkfs: clust num %d no valid! clust size %d sec\n", clust_num, clust_size);
                    return FSTU_MKFS_ABORT;
                }
            }
            else
            {
                // FAT volume
                if (!pclust_size)
                {
                    n = vol_size / 0x1000; // volume size is unit of 4KS
                    for (i = 0, pclust_size = 1; cst[i] && cst[i] <= n; i++, pclust_size <<= 1)
                        ;
                }
                clust_num = vol_size / pclust_size;
                if (clust_num > FAT12_CLUST_MAX)
                    n = clust_num * 2 + 4; // fat size[bytes]
                else
                {
                    fstype = FS_FAT12;
                    n = (clust_num * 3 + 1) / 2 + 3; // fat size[bytes]
                }
                fat_size = (n + SECTOR_SIZE - 1) / SECTOR_SIZE;
                reserved_size = 1;
                dir_size = (rootdir_num)*DIR_TERN_SIZE / SECTOR_SIZE; // root dir size (sector)
            }
            fat_start = vol_start + reserved_size;
            data_start = fat_start + fat_size * fat_num + dir_size;

            // align data area to block bounary
            n = (data_start + (block_size - 1) & ~(block_size - 1)) - data_start;
            if (fstype == FS_FAT32) // alloc free block
            {
                reserved_size += n;
                fat_start += n;
            }
            else
            { // exFat
                if (n % fat_num)
                {
                    n--;
                    reserved_size++;
                    fat_start++;
                }
                fat_size += n / fat_num;
            }

            // check vol size
            if (vol_size < data_start + pclust_size * 16 - vol_start) // vol too small
            {
                KPrint("[fat] mkfs: vol too small\n");
                return FSTU_MKFS_ABORT;
            }

            clust_num = (vol_size - reserved_size - fat_size * fat_num - dir_size) / pclust_size; // data area clust number

            KPrint("[fat] mkfs: data area clusts %d\n", clust_num);

            if (fstype == FS_FAT32)
            {
                if (clust_num <= FAT16_CLUST_MAX) // too few clust for FAT32
                {
                    if (!clust_size && (clust_size = pclust_size / 2) != 0)
                        continue;

                    KPrint("[fat] mkfs: too few clust for fat32\n");
                    return FSTU_MKFS_ABORT;
                }
            }
            if (fstype == FS_FAT16)
            {
                if (clust_num > FAT16_CLUST_MAX) // too many clust for FAT16
                {
                    if (!clust_size && (clust_size = pclust_size * 2) <= 64 != 0)
                        continue;

                    if (fsop & FS_FAT32)
                        fstype = FS_FAT32; // switch to fat32

                    KPrint("[fat] mkfs: too many clust for fat16\n");
                    return FSTU_MKFS_ABORT;
                }
            }
            if (fstype == FS_FAT12)
            {
                if (clust_num > FAT12_CLUST_MAX) // too many clust for FAT12
                {
                    KPrint("[fat] mkfs: too many clust for fat12\n");
                    return FSTU_MKFS_ABORT;
                }
            }
            break;
        }

        // create FAT vbr
        memset(buff, 0, SECTOR_SIZE);
        // create vbr in buffer
        memcpy(((bpb_t *)buff)->jmp, "\xEB\xFE\x90"
                                     "CATOS0.1",
               11);                                                               // boot jump code and oem
        ((bpb_t *)buff)->bytes_per_sec = SECTOR_SIZE;                             // sector size[byte]
        ((bpb_t *)buff)->sec_per_clust = pclust_size;                             // clust size[sector]
        ((bpb_t *)buff)->reserved_sec = reserved_size;                            // reserved sector size[sector]
        ((bpb_t *)buff)->fat_num = fat_num;                                       // number of FAT
        ((bpb_t *)buff)->rootdir_number = (fstype == FS_FAT32) ? 0 : rootdir_num; // number of root director entries(FAT32 is 0)
        if (vol_size < 0x10000)
        {
            ((bpb_t *)buff)->total_sector = vol_size; // volume size in 16bit LBA
        }
        else
        {
            ((bpb_t *)buff)->large_sector = vol_size; // volume size in 32bit LBA
        }
        ((bpb_t *)buff)->media = 0xF8;          // media descript byte
        ((bpb_t *)buff)->sec_per_track = 63;    // number of sectors per track
        ((bpb_t *)buff)->head = 255;            // number of heads
        ((bpb_t *)buff)->start_lba = vol_start; // volume offset in physical driver
        if (fstype == FS_FAT32)
        {
            ((bpb_t *)buff)->exbpb.fat32.volumeID = GetFatTime();
            ((bpb_t *)buff)->exbpb.fat32.fat_size32 = fat_size;                  // size of fat
            ((bpb_t *)buff)->exbpb.fat32.rootdir_clusters = 2;                   // root director cluster
            ((bpb_t *)buff)->exbpb.fat32.fsinfo_sector = 1;                      // FSInfo block start sector
            ((bpb_t *)buff)->exbpb.fat32.backup_sector = 6;                      // backup sector
            ((bpb_t *)buff)->exbpb.fat32.driver = 0x80;                          // driver number
            ((bpb_t *)buff)->exbpb.fat32.signature = 0x29;                       // signature
            memcpy(((bpb_t *)buff)->exbpb.fat32.volume_label, "Local Vol ", 11); // volume label string
            memcpy(((bpb_t *)buff)->exbpb.fat32.sysid_string, "FAT32    ", 8);   // sysid string
        }
        else
        {
            ((bpb_t *)buff)->exbpb.fat.volumeID = GetFatTime();
            ((bpb_t *)buff)->fat_size16 = fat_size;
            ((bpb_t *)buff)->exbpb.fat.driver = 0x80;
            ((bpb_t *)buff)->exbpb.fat.signature = 0x29;
            memcpy(((bpb_t *)buff)->exbpb.fat.volume_label, "Local Vol ", 11);
            memcpy(((bpb_t *)buff)->exbpb.fat.sysid_string, "FAT     ", 8);
        }
        ((bpb_t *)buff)->magic = 0xaa55;                   // magic
        if (DiskWrite(vol, buff, vol_start, 1) != DSTU_OK) // write VBR to disk
        {
            KPrint("[fat] mkfs: write FAT VBR error!\n");
            return FSTU_DISK_ERR;
        }

        // create FSINFO block(FAT32)
        if (fstype == FS_FAT32)
        {
            KPrint("[fat] mkfs: create FSINFO block for FAT32\n");
            // write VBR to backup sector
            if (DiskWrite(pdrv, buff, vol_start + 6, 1) != DSTU_OK)
                return FSTU_DISK_ERR;

            memset(buff, 0, SECTOR_SIZE);
            ((fsinfo_t *)buff)->lead_signature = 0x41615252;       // must be 0x41615252
            ((fsinfo_t *)buff)->another_signature = 0x61417272;    // must be 0x61417272
            ((fsinfo_t *)buff)->free_cluster = clust_num - 1;      // number of free clusters
            ((fsinfo_t *)buff)->next_cluster = 2;                  // last allocation cluster
            ((fsinfo_t *)buff)->signature = 0xAA550000;            // magic
            if (DiskWrite(vol, buff, vol_start + 1, 1) != DSTU_OK) // write FSINFO to disk
                return FSTU_DISK_ERR;
            if (DiskWrite(vol, buff, vol_start + 7, 1) != DSTU_OK) // write FSINFO to backup sector
                return FSTU_DISK_ERR;
        }

        // initialization FAT
        uint64_t sector, sector_num;
        memset(buff, 0, SECTOR_SIZE);
        sector = fat_start;
        for (i = 0; i < fat_num; i++) // set each FATs
        {
            if (fstype == FS_FAT32)
            {
                *(uint32_t *)(buff + 0) = 0xFFFFFFF8; // FAT[0]
                *(uint32_t *)(buff + 4) = 0xFFFFFFFF; // FAT[1]
                *(uint32_t *)(buff + 8) = 0x0FFFFFFF; // FAT[2](root director)
            }
            else // FAT or FAT16
            {
                *(uint32_t *)(buff + 0) = (fstype == FS_FAT12) ? 0xFFFFF8 : 0xFFFFFFF8;
            }

            sector_num = fat_size;
            do // fill FAT sector
            {
                if (DiskWrite(vol, buff, sector, 1) != DSTU_OK)
                    return FSTU_DISK_ERR;
                memset(buff, 0, SECTOR_SIZE); // reset of FAT all sector are clear
                sector++;
                sector_num--;
            } while (sector_num > 0);
        }

        // initialization root director
        // fat32 init the front two of clusters
        sector_num = (fstype = FS_FAT32) ? pclust_size : dir_size;
        do
        {
            memset(buff, 0, SECTOR_SIZE);
            if (DiskWrite(vol, buff, sector, 1) != DSTU_OK)
                return FSTU_DISK_ERR;
            sector++;
            sector_num--;
        } while (sector_num);

        // determine systemID in the MBR partition table
        uint8_t sys;
        if (fstype == FS_EXFAT)
        {
            sys = 0x07; // exFAT
        }
        else
        {
            if (fstype == FS_FAT32)
            {
                sys = 0x0C; // FAT32
            }
            else
            {
                if (vol_size >= 0x10000)
                    sys = 0x06; // FAT12/16(large)
                else
                    sys = (fstype == FS_FAT16) ? 0x04 : 0x01; // FAT16:FAT12
            }
        }
        KPrint("[fat] mkfs: sysid type %d\n", sys);

        // update partition information
        if (part != 0xff) // update vol info on partition table(part != 0xff respect physical disk)
        {
            if (!(fsop & 0x80))
            {
                KPrint("[fat] mkfs: update partition info!\n");
                if (DiskRead(pdrv, buff, 0, 1) != DSTU_OK)
                    return FSTU_DISK_ERR;
                // only support mbr partition
                ((mbr_t *)buff)->partition[i - 1].type = sys;
                if (DiskWrite(part, buff, 0, 1) != DSTU_OK)
                    return FSTU_DISK_ERR;
            }
            else
            {
                if (!(fsop & FM_SFD)) // create partition table
                {
                    KPrint("[fat] mkfs: create partition table\n");
                    lba[0] = vol_size;
                    lba[1] = 0;
                    // create partition on pysical driver
                    res = CreatePartition(pdrv, lba, sys, buff);
                    if (res != FSTU_OK)
                        return res;
                }
            }
        }
    }

    if (DiskIoCtl(vol, CTRL_SYNC, 0) != DSTU_OK)
    {
        KPrint("[fat] disk CTRL_SYNC failed when mkfs\n");
        return FSTU_DISK_ERR;
    }
    KPrint("[fat] mkfs on device success\n");
    return FSTU_OK;
}

// create a object name in director object

static file_status_t CreateName(                   /* FR_OK: successful, FR_INVALID_NAME: could not create */
                                dir_obj_t *dp,     /* Pointer to the directory object */
                                const TCHAR **path /* Pointer to pointer to the segment in the path string */
)
{
    BYTE b,
        cf;
    WCHAR wc, *lfn;
    DWORD uc;
    uint32_t i, ni, si, di;
    const TCHAR *p;

    /* Create LFN into LFN working buffer */
    p = *path;
    lfn = dp->obj.fs->lfn_buff;
    di = 0;
    for (;;)
    {
        uc = Tchar2Uni(&p); /* Get a character */
        if (uc == 0xFFFFFFFF)
            return FSTU_INVALID_NAME; /* Invalid code or UTF decode error */
        if (uc >= 0x10000)
            lfn[di++] = (WCHAR)(uc >> 16); /* Store high surrogate if needed */
        wc = (WCHAR)uc;
        if (wc < ' ' || wc == '/' || wc == '\\')
            break; /* Break if end of the path or a separator is found */
        if (wc < 0x80 && CheckChar("\"*:<>\?|\x7F", wc))
            return FSTU_INVALID_NAME; /* Reject illegal characters for LFN */
        if (di >= FS_LFN_LEN)
            return FSTU_INVALID_NAME; /* Reject too long name */
        lfn[di++] = wc;               /* Store the Unicode character */
    }
    if (wc < ' ')
    {                 /* End of path? */
        cf = NS_LAST; /* Set last segment flag */
    }
    else
    {
        cf = 0; /* Next segment follows */
        while (*p == '/' || *p == '\\')
            p++; /* Skip duplicated separators if exist */
    }
    *path = p; /* Return pointer to the next segment */

    if ((di == 1 && lfn[di - 1] == '.') ||
        (di == 2 && lfn[di - 1] == '.' && lfn[di - 2] == '.'))
    { /* Is this segment a dot name? */
        lfn[di] = 0;
        for (i = 0; i < 11; i++)
        { /* Create dot name for SFN entry */
            dp->fn[i] = (i < di) ? '.' : ' ';
        }
        dp->fn[i] = cf | NS_DOT; /* This is a dot entry */
        return FSTU_OK;
    }

    while (di)
    { /* Snip off trailing spaces and dots if exist */
        wc = lfn[di - 1];
        if (wc != ' ' && wc != '.')
            break;
        di--;
    }
    lfn[di] = 0; /* LFN is created into the working buffer */
    if (di == 0)
        return FSTU_INVALID_NAME; /* Reject null name */

    /* Create SFN in directory form */
    for (si = 0; lfn[si] == ' '; si++)
        ; /* Remove leading spaces */
    if (si > 0 || lfn[si] == '.')
        cf |= NS_LOSS | NS_LFN; /* Is there any leading space or dot? */
    while (di > 0 && lfn[di - 1] != '.')
        di--; /* Find last dot (di<=si: no extension) */

    memset(dp->fn, ' ', 11);
    i = b = 0;
    ni = 8;
    for (;;)
    {
        wc = lfn[si++]; /* Get an LFN character */
        if (wc == 0)
            break; /* Break on end of the LFN */
        if (wc == ' ' || (wc == '.' && si != di))
        { /* Remove embedded spaces and dots */
            cf |= NS_LOSS | NS_LFN;
            continue;
        }

        if (i >= ni || si == di)
        { /* End of field? */
            if (ni == 11)
            { /* Name extension overflow? */
                cf |= NS_LOSS | NS_LFN;
                break;
            }
            if (si != di)
                cf |= NS_LOSS | NS_LFN; /* Name body overflow? */
            if (si > di)
                break; /* No name extension? */
            si = di;
            i = 8;
            ni = 11;
            b <<= 2; /* Enter name extension */
            continue;
        }

        if (wc >= 0x80)
        {                 /* Is this a non-ASCII character? */
            cf |= NS_LFN; /* LFN entry needs to be created */

            wc = FsUni2Oem(wc, CODEPAGE); /* Unicode ==> ANSI/OEM code */
            if (wc & 0x80)
                wc = ExCvt[wc & 0x7F]; /* Convert extended character to upper (SBCS) */

            wc = FsUni2Oem(FsWtoupper(wc), CODEPAGE); /* Unicode ==> Upper convert ==> ANSI/OEM code */
        }

        if (wc >= 0x100)
        { /* Is this a DBC? */
            if (i >= ni - 1)
            { /* Field overflow? */
                cf |= NS_LOSS | NS_LFN;
                i = ni;
                continue; /* Next field */
            }
            dp->fn[i++] = (uint8_t)(wc >> 8); /* Put 1st byte */
        }
        else
        { /* SBC */
            if (wc == 0 || CheckChar("+,;=[]", wc))
            { /* Replace illegal characters for SFN if needed */
                wc = '_';
                cf |= NS_LOSS | NS_LFN; /* Lossy conversion */
            }
            else
            {
                if (IsUpper(wc))
                { /* ASCII upper case? */
                    b |= 2;
                }
                if (IsLower(wc))
                { /* ASCII lower case? */
                    b |= 1;
                    wc -= 0x20;
                }
            }
        }
        dp->fn[i++] = (BYTE)wc;
    }

    if (dp->fn[0] == DDEM)
        dp->fn[0] = RDDEM; /* If the first character collides with DDEM, replace it with RDDEM */

    if (ni == 8)
        b <<= 2; /* Shift capital flags if no extension */
    if ((b & 0x0C) == 0x0C || (b & 0x03) == 0x03)
        cf |= NS_LFN; /* LFN entry needs to be created if composite capitals */
    if (!(cf & NS_LFN))
    { /* When LFN is in 8.3 format without extended character, NT flags are created */
        if (b & 0x01)
            cf |= NS_EXT; /* NT flag (Extension has small capital letters only) */
        if (b & 0x04)
            cf |= NS_BODY; /* NT flag (Body has small capital letters only) */
    }           


    dp->fn[NSFLAGS] = cf; /* SFN is created into dp->fn[] */

    return FSTU_OK;
}

// checksum
static uint16_t XdirSum(const uint8_t *dir)
{
    uint32_t i, block_size;
    uint16_t sum;

    block_size = (((xdir_info_t *)(dir))->xdir_dir.xdir_primary.num_dir + 1) * DIR_TERN_SIZE; // number of bytes of the entry block
    for (i = sum = 0; i < block_size; i++)
    {
        if (i == XDIR_SUM)
        {
            i += 2; // skip 2 byte sum filed
        }
        else
        {
            sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + dir[i];
        }
    }
    return sum;
}

static uint16_t XnameSum(const uint16_t *name)
{
    uint16_t ch;
    uint16_t sum = 0;

    while ((ch = *name++))
    {
        ch = (uint16_t)FsWtoupper(ch); // file name need to be up-case coverted
        sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + (ch & 0xFF);
        sum = ((sum & 1) ? 0x8000 : 0) + (sum >> 1) + (ch >> 8);
    }
    return sum;
}

static uint32_t XSum32(uint8_t data, uint32_t sum)
{
    sum = ((sum & 1) ? 0x80000000 : 0) + (sum >> 1) + data;
    return sum;
}

static void CreateXdir(uint8_t *dir, const uint16_t *lfn)
{
    uint32_t i;
    uint8_t count, len;
    uint16_t wc;

    // create file/director and stream-extension entry
    memset(dir, 0, 2 * DIR_TERN_SIZE);
    ((xdir_info_t *)(dir + 0 * DIR_TERN_SIZE))->type = EXFAT_FILEDIR;
    ((xdir_info_t *)(dir + 1 * DIR_TERN_SIZE))->type = EXFAT_STREAM;

    // create file-name entry
    i = DIR_TERN_SIZE * 2; // top of file_name entries
    len = count = 0;
    wc = 1;
    do
    {
        dir[i++] = EXFAT_FILENAME;
        dir[i++] = 0;
        do
        {
            if (wc && (wc = lfn[len]))
                len++;
            *(uint16_t *)(dir + i) = wc; // store char
            i += 2;
        } while (i % DIR_TERN_SIZE);
        count++;
    } while (lfn[len]); // fill next entry if lfn had any char

    ((xdir_info_t *)dir)->xdir_dir.xdir_seconday.name_len = len;
    ((xdir_info_t *)dir)->xdir_dir.xdir_primary.num_dir = count + 1;
    ((xdir_info_t *)dir)->xdir_dir.xdir_seconday.name_chksum = XnameSum(lfn);
}

/** put a utf character
 *@return: return number of put characters
 **/
static uint8_t PutUtf(uint32_t ch, uint8_t *buff, uint32_t size)
{
    uint16_t wc;

    wc = FsUni2Oem(ch, CODEPAGE);
    if (wc >= 0x100) // is a DBC
    {
        if (size < 2)
            return 0;
        *buff++ = (uint8_t)(wc >> 8); // store DBC 1st bytes
        *buff++ = (uint8_t)(wc);      // store DBC 2nd bytes
        return 2;
    }
    if (!wc || size < 1) // invalid char or buffer overflow
        return 0;
    *buff++ = (TCHAR)wc; // store the character
    return 1;
}
